diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.8.0 (2024-06-11)
+* no longer use `Validation`: failures now must wrap explicitly instead of being
+  implicitly collated
+* fix some bounds (`text` lower bound was too low)
+* various tweaks
+
 ## 0.7.1 (2024-05-27)
 * bump meta/dependencies
 
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2022 Ben Orchard (@raehik) <thefirstmuffinman@gmail.com>
+Copyright (c) 2022-2024 Ben Orchard (@raehik) <thefirstmuffinman@gmail.com>
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/src/Strongweak/Strengthen.hs b/src/Strongweak/Strengthen.hs
--- a/src/Strongweak/Strengthen.hs
+++ b/src/Strongweak/Strengthen.hs
@@ -6,13 +6,13 @@
   -- * 'Strengthen' class
     Strengthen(..)
   , restrengthen
-  , Result
 
   -- ** Helpers
   , strengthenBounded
 
   -- * Strengthen failures
   , StrengthenFailure(..)
+  , StrengthenFailure'
   , failStrengthen1
   , failStrengthen
 
@@ -20,11 +20,8 @@
   , Strongweak.Weaken.Weak
   ) where
 
-import Strongweak.Util.Typeable ( typeRep' )
 import Strongweak.Util.TypeNats ( natVal'' )
 import Strongweak.Weaken ( Weaken(..) )
-import Data.Either.Validation
-import Data.Typeable ( Typeable )
 
 import GHC.TypeNats ( KnownNat )
 import Data.Word
@@ -42,7 +39,7 @@
 
 import Data.Bits ( FiniteBits )
 
-type Result = Validation StrengthenFailure
+import Data.Typeable ( Typeable, TypeRep, typeRep, Proxy(Proxy) )
 
 {- | Attempt to strengthen some @'Weak' a@, asserting certain invariants.
 
@@ -57,7 +54,7 @@
 class Weaken a => Strengthen a where
     -- | Attempt to strengthen some @'Weak' a@ to its associated strong type
     --   @a@.
-    strengthen :: Weak a -> Result a
+    strengthen :: Weak a -> Either StrengthenFailure' a
 
 -- | Weaken a strong value, then strengthen it again.
 --
@@ -66,35 +63,48 @@
 -- invariants. For example:
 --
 -- >>> restrengthen $ unsafeStrengthen @(Vector 2 Natural) [0]
--- Failure ...
-restrengthen
-    :: (Strengthen a, Weaken a)
-    => a -> Result a
+-- Left ...
+restrengthen :: (Strengthen a, Weaken a) => a -> Either StrengthenFailure' a
 restrengthen = strengthen . weaken
 
 -- | A failure encountered during strengthening.
-data StrengthenFailure = StrengthenFailure
-  { strengthenFailDetail :: [TBL.Builder]
+--
+-- Strengthening can involve multiple distinct checks. In such cases, you may
+-- record multiple failures in a single 'StrengthenFailure' by placing them in
+-- the inner failure list and noting their meaning in the detail field.
+data StrengthenFailure text = StrengthenFailure
+  { strengthenFailDetail :: [text]
   -- ^ Detail on strengthen failure.
   --
   -- We use a list here for the cases where you want multiple lines of detail.
   -- Separating with a newline would make prettifying later harder, so we delay.
+  --
+  -- Note that this should probably never be empty. TODO consider @NonEmpty@,
+  -- but fairly unimportant.
 
-  , strengthenFailInner  :: [(TBL.Builder, StrengthenFailure)]
-  -- ^ Indexed.
+  , strengthenFailInner  :: [(text, StrengthenFailure text)]
+  -- ^ Optional wrapped failures.
+  --
+  -- The @text@ type acts as an index. Its meaning depends on the failure
+  -- in question, and should be explained in 'strengthenFailDetail'.
   } deriving stock Show
 
+type StrengthenFailure' = StrengthenFailure TBL.Builder
+
+-- | Shorthand for failing a strengthen.
 failStrengthen
-    :: [TBL.Builder] -> [(TBL.Builder, StrengthenFailure)] -> Result a
-failStrengthen t fs = Failure $ StrengthenFailure t fs
+    :: [text] -> [(text, StrengthenFailure text)]
+    -> Either (StrengthenFailure text) a
+failStrengthen t fs = Left $ StrengthenFailure t fs
 
-failStrengthen1 :: [TBL.Builder] -> Result a
+-- | Shorthand for failing a strengthen with no inner failures.
+failStrengthen1 :: [text] -> Either (StrengthenFailure text) a
 failStrengthen1 t = failStrengthen t []
 
 -- | Strengthen a type by refining it with a predicate.
 instance Refine p a => Strengthen (Refined p a) where
     strengthen = refine .> \case
-      Right ra -> Success ra
+      Right ra -> Right ra
       Left  rf -> failStrengthen1
         [ "refinement failure:"
         , TBL.fromText (prettyRefineFailure rf) ]
@@ -103,7 +113,7 @@
 -- | Strengthen a type by refining it with a functor predicate.
 instance Refine1 p f => Strengthen (Refined1 p f a) where
     strengthen = refine1 .> \case
-      Right ra -> Success ra
+      Right ra -> Right ra
       Left  rf -> failStrengthen1
         [ "refinement failure:"
         , TBL.fromText (prettyRefineFailure rf) ]
@@ -111,7 +121,7 @@
 -- | Strengthen a plain list into a non-empty list by asserting non-emptiness.
 instance Strengthen (NonEmpty a) where
     strengthen = NonEmpty.nonEmpty .> \case
-      Just neas -> Success neas
+      Just neas -> Right neas
       Nothing   -> failStrengthen1 $
         [ "type: [a] -> NonEmpty a"
         , "fail: empty list" ]
@@ -121,7 +131,7 @@
     -- TODO another case of TBL not supporting unbounded integrals
     strengthen as =
         case VGS.fromList as of
-          Just va -> Success va
+          Just va -> Right va
           Nothing -> failStrengthen1 $
             [ "type: [a] -> Vector v "<>fromString (show n)<>" a"
             , "fail: wrong length (got "<>TBL.fromDec (length as)<>")" ]
@@ -129,11 +139,11 @@
 
 -- | Add wrapper.
 instance Strengthen (Identity a) where
-    strengthen = Success . Identity
+    strengthen = Right . Identity
 
 -- | Add wrapper.
 instance Strengthen (Const a b) where
-    strengthen = Success . Const
+    strengthen = Right . Const
 
 {- TODO controversial. seems logical, but also kinda annoying.
 instance (Show a, Typeable a) => Strengthen (Maybe a) where
@@ -165,9 +175,9 @@
     :: forall m n
     .  ( Typeable n, Integral n, Show n
        , Typeable m, Integral m, Bounded m, FiniteBits m
-       ) => n -> Result m
+       ) => n -> Either StrengthenFailure' m
 strengthenBounded n
-  | n <= maxBn && n >= minBn = Success (fromIntegral n)
+  | n <= maxBn && n >= minBn = Right (fromIntegral n)
   | otherwise = failStrengthen1
         [ "numeric strengthen: "<>fromString (show (typeRep' @n))
           <>" -> "<>fromString (show (typeRep' @m))
@@ -187,36 +197,36 @@
 instance Strengthen a => Strengthen [a] where
     strengthen = strengthenList
 
-strengthenList :: Strengthen a => [Weak a] -> Result [a]
+strengthenList :: Strengthen a => [Weak a] -> Either StrengthenFailure' [a]
 strengthenList = goR (0 :: Int) [] . map strengthen
   where
     goR i as = \case
       r:rs ->
         case r of
-          Success a -> goR (i+1) (a:as) rs
-          Failure e -> goL (i+1) [(TBL.fromDec i, e)]    rs
-      []   -> Success as
+          Right a -> goR (i+1) (a:as) rs
+          Left  e -> goL (i+1) [(TBL.fromDec i, e)]    rs
+      []   -> Right as
     goL i es = \case
       r:rs ->
         case r of
-          Success _ -> goL (i+1) es                      rs
-          Failure e -> goL (i+1) ((TBL.fromDec i, e):es) rs
+          Right _ -> goL (i+1) es                      rs
+          Left  e -> goL (i+1) ((TBL.fromDec i, e):es) rs
       []   -> failStrengthen ["list had failures"] es
 
 -- | Decomposer. Strengthen both elements of a tuple.
 instance (Strengthen l, Strengthen r) => Strengthen (l, r) where
     strengthen (l, r) =
         case strengthen l of
-          Success sl ->
+          Right sl ->
             case strengthen r of
-              Success sr -> Success (sl, sr)
-              Failure er -> failStrengthen ["2-tuple: right failed"]
+              Right sr -> Right (sl, sr)
+              Left  er -> failStrengthen ["2-tuple: right failed"]
                 [("R", er)]
-          Failure el ->
+          Left  el ->
             case strengthen @r r of
-              Success _  -> failStrengthen ["2-tuple:  left failed"]
+              Right _  -> failStrengthen ["2-tuple:  left failed"]
                 [("L", el)]
-              Failure er -> failStrengthen ["2-tuple:   l&r failed"]
+              Left  er -> failStrengthen ["2-tuple:   l&r failed"]
                 [("R", er), ("L", el)]
 
 -- | Decomposer. Strengthen either side of an 'Either'.
@@ -229,3 +239,6 @@
 -- from flow
 (.>) :: (a -> b) -> (b -> c) -> a -> c
 f .> g = g . f
+
+typeRep' :: forall a. Typeable a => TypeRep
+typeRep' = typeRep (Proxy @a)
diff --git a/src/Strongweak/Strengthen/Generic.hs b/src/Strongweak/Strengthen/Generic.hs
--- a/src/Strongweak/Strengthen/Generic.hs
+++ b/src/Strongweak/Strengthen/Generic.hs
@@ -17,46 +17,28 @@
 
 module Strongweak.Strengthen.Generic where
 
+import Data.Kind ( type Constraint )
 import Strongweak.Strengthen
 import GHC.Generics
-import Data.Either.Validation
 import Data.Kind ( Type )
 import GHC.TypeNats ( Natural, type (+), KnownNat )
-import Control.Applicative qualified as A -- liftA2 export workaround
 import Strongweak.Util.TypeNats ( natVal'' )
 import Data.Text.Builder.Linear qualified as TBL
 import GHC.Exts ( Symbol, fromString, proxy# )
 import GHC.TypeLits ( KnownSymbol, symbolVal' )
 
-{- TODO
-So, now that we're improving the error story, we can do so here as well.
-
-At product level in these generics, we know that neither data type names or
-constructor names (weak or strong) will change. So individual fields can simply
-annotate themselves with the weak & strong field identifiers. Then those can get
-wrapped into a nice clean error higher up, that says "this constructor had the
-following errors".
-
-It's gonna look like the tuple and list 'Strengthen' instances but worse. Lots
-of fiddly stuff.
-
-Also, we can do the data type equality check I noted earlier. If weak & strong
-data type names/constructor names match, we're probably doing @SW@ tricks, and
-could probably shorten the error a bit.
--}
-
 -- | Strengthen a value generically.
 --
 -- The weak and strong types must be /compatible/. See 'Strongweak.Generic' for
 -- the definition of compatibility in this context.
 strengthenGeneric
     :: (Generic w, Generic s, GStrengthenD (Rep w) (Rep s))
-    => w -> Result s
+    => w -> Either StrengthenFailure' s
 strengthenGeneric = fmap to . gstrengthenD . from
 
 -- | Generic strengthening at the datatype level.
 class GStrengthenD w s where
-    gstrengthenD :: w p -> Result (s p)
+    gstrengthenD :: w p -> Either StrengthenFailure' (s p)
 
 -- | Strengthen a generic data type, replacing its metadata wrapping.
 instance GStrengthenC wdn sdn w s
@@ -67,10 +49,10 @@
 
 -- | Generic strengthening at the constructor sum level.
 class GStrengthenC (wdn :: Symbol) (sdn :: Symbol) w s where
-    gstrengthenC :: w p -> Result (s p)
+    gstrengthenC :: w p -> Either StrengthenFailure' (s p)
 
 -- | Nothing to do for empty datatypes.
-instance GStrengthenC wdn sdn V1 V1 where gstrengthenC = Success
+instance GStrengthenC wdn sdn V1 V1 where gstrengthenC = Right
 
 -- | Strengthen sum types by casing and strengthening left or right.
 instance
@@ -87,11 +69,11 @@
         (C1 (MetaCons scn _smc2 _smc3) s) where
     gstrengthenC (M1 w) =
         case gstrengthenS @0 w of
-          Success s  -> Success (M1 s)
-          Failure es -> failStrengthen [reifyCstrs @wcd @wcn @scd @scn] es
+          Right s -> Right (M1 s)
+          Left  e -> failStrengthen [reifyCstrs @wcd @wcn @scd @scn] e
 
-class ReifyCstrs (ld :: Symbol) (lc :: Symbol) (rd :: Symbol) (rc :: Symbol) where
-    reifyCstrs :: TBL.Builder
+type ReifyCstrs :: Symbol -> Symbol -> Symbol -> Symbol -> Constraint
+class ReifyCstrs ld lc rd rc where reifyCstrs :: TBL.Builder
 
 -- | Special case: data type and constructor names are equivalent: simplify
 instance {-# OVERLAPPING #-} (KnownSymbol d, KnownSymbol c)
@@ -114,10 +96,10 @@
 
 -- | Generic strengthening at the constructor level.
 class GStrengthenS (i :: Natural) w s where
-    gstrengthenS :: w p -> Validation [(TBL.Builder, StrengthenFailure)] (s p)
+    gstrengthenS :: w p -> Either [(TBL.Builder, StrengthenFailure')] (s p)
 
 -- | Nothing to do for empty constructors.
-instance GStrengthenS i U1 U1 where gstrengthenS = Success
+instance GStrengthenS i U1 U1 where gstrengthenS = Right
 
 -- | Strengthen product types by strengthening left and right.
 instance
@@ -125,9 +107,17 @@
   , GStrengthenS (i + ProdArity wl) wr sr
   ) => GStrengthenS i (wl :*: wr) (sl :*: sr) where
     gstrengthenS (l :*: r) =
-        A.liftA2 (:*:)
-               (gstrengthenS @i                  l)
-               (gstrengthenS @(i + ProdArity wl) r)
+        -- With @Validation@, @A.liftA2 (:*:)@. But I don't have a use for it
+        -- elsewhere, so let's just be explicit.
+        case gstrengthenS @i l of
+          Right la ->
+            case gstrengthenS @(i + ProdArity wl) r of
+              Right  ra -> Right (la :*: ra)
+              Left   re -> Left re
+          Left  le ->
+            case gstrengthenS @(i + ProdArity wl) @_ @sr r of
+              Right _ra -> Left le
+              Left   re -> Left (le <> re)
 
 -- | Special case: if source and target types are equivalent, just replace meta.
 --
@@ -137,7 +127,7 @@
 instance {-# OVERLAPPING #-} GStrengthenS i
   (S1 (MetaSel _wms1 _wms2 _wms3 _wms4) (Rec0 a))
   (S1 (MetaSel _sms1 _sms2 _sms3 _sms4) (Rec0 a)) where
-    gstrengthenS = Success . M1 . unM1
+    gstrengthenS = Right . M1 . unM1
 
 -- | Strengthen a field using the existing 'Strengthen' instance.
 instance
@@ -148,8 +138,8 @@
         (S1 (MetaSel wmr _wms2 _wms3 _wms4) (Rec0 w))
         (S1 (MetaSel smr _sms2 _sms3 _sms4) (Rec0 s)) where
     gstrengthenS = unM1 .> unK1 .> strengthen .> \case
-      Success s -> Success $ M1 $ K1 s
-      Failure e -> Failure [(reifySelector @i @wmr @smr, e)]
+      Right s -> Right $ M1 $ K1 s
+      Left  e -> Left  [(reifySelector @i @wmr @smr, e)]
 
 {- TODO
 * how to separate index and record name? @.@ is good and bad, uses same syntax
diff --git a/src/Strongweak/Util/Text.hs b/src/Strongweak/Util/Text.hs
deleted file mode 100644
--- a/src/Strongweak/Util/Text.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Strongweak.Util.Text where
-
-import Data.Text qualified as Text
-import Data.Text ( Text )
-
-tshow :: Show a => a -> Text
-tshow = Text.pack . show
diff --git a/src/Strongweak/Util/Typeable.hs b/src/Strongweak/Util/Typeable.hs
deleted file mode 100644
--- a/src/Strongweak/Util/Typeable.hs
+++ /dev/null
@@ -1,8 +0,0 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-
-module Strongweak.Util.Typeable where
-
-import Data.Typeable
-
-typeRep' :: forall a. Typeable a => TypeRep
-typeRep' = typeRep (Proxy @a)
diff --git a/strongweak.cabal b/strongweak.cabal
--- a/strongweak.cabal
+++ b/strongweak.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           strongweak
-version:        0.7.1
+version:        0.8.0
 synopsis:       Convert between strong and weak representations of types
 description:    Please see README.md.
 category:       Data
@@ -34,8 +34,6 @@
       Strongweak.Strengthen
       Strongweak.Strengthen.Generic
       Strongweak.Strengthen.Unsafe
-      Strongweak.Util.Text
-      Strongweak.Util.Typeable
       Strongweak.Util.TypeNats
       Strongweak.Weaken
       Strongweak.Weaken.Generic
@@ -57,12 +55,11 @@
   ghc-options: -Wall -Wno-unticked-promoted-constructors
   build-depends:
       base >=4.18 && <5
-    , either >=5.0.1.1 && <5.1
     , rerefined >=0.5.0 && <0.6
-    , text >=1.2.5.0 && <2.2
+    , text >=2.0 && <2.2
     , text-builder-linear >=0.1.2 && <0.2
     , vector >=0.12.3.1 && <0.14
-    , vector-sized >=1.5.0 && <1.6
+    , vector-sized >=1.5.0 && <1.7
   default-language: GHC2021
 
 test-suite spec
@@ -91,14 +88,13 @@
   build-depends:
       QuickCheck >=2.14.2 && <2.16
     , base >=4.18 && <5
-    , either >=5.0.1.1 && <5.1
     , generic-random >=1.5.0.1 && <1.6
     , hspec >=2.7 && <2.12
     , quickcheck-instances >=0.3.26 && <0.4
     , rerefined >=0.5.0 && <0.6
     , strongweak
-    , text >=1.2.5.0 && <2.2
+    , text >=2.0 && <2.2
     , text-builder-linear >=0.1.2 && <0.2
     , vector >=0.12.3.1 && <0.14
-    , vector-sized >=1.5.0 && <1.6
+    , vector-sized >=1.5.0 && <1.7
   default-language: GHC2021
diff --git a/test/Common.hs b/test/Common.hs
--- a/test/Common.hs
+++ b/test/Common.hs
@@ -1,7 +1,7 @@
 module Common where
 
 import Strongweak
-import Strongweak.Strengthen qualified as Strengthen
+import Strongweak.Strengthen
 import Strongweak.Generic
 import Rerefined
 import Rerefined.Predicates
@@ -11,7 +11,6 @@
 import Numeric.Natural ( Natural )
 import Data.Word
 import Test.QuickCheck.Instances.Natural()
-import Data.Either.Validation
 
 data DS (s :: Strength)
   = DS0 (SW s Word8) (SW s Word8) Word8 (SW s Word8) (SW s Word8)
@@ -52,5 +51,5 @@
     weaken = weakenGeneric
 instance Strengthen (DP 'Strong) where strengthen = strengthenGeneric
 
-tryStrengthenSuccessEq :: Eq a => a -> Strengthen.Result a -> Bool
-tryStrengthenSuccessEq a = \case Success a' -> a == a'; Failure{} -> False
+tryStrengthenSuccessEq :: Eq a => a -> Either StrengthenFailure' a -> Bool
+tryStrengthenSuccessEq a = \case Right a' -> a == a'; Left{} -> False
diff --git a/test/Strongweak/LawsSpec.hs b/test/Strongweak/LawsSpec.hs
--- a/test/Strongweak/LawsSpec.hs
+++ b/test/Strongweak/LawsSpec.hs
@@ -2,7 +2,6 @@
 
 import Strongweak
 import Common
-import Data.Either.Validation
 import Test.Hspec
 import Test.Hspec.QuickCheck
 
@@ -14,6 +13,6 @@
     prop "strengthen-weaken-strengthen roundtrip partial isomorphism (generic)" $ do
       \(dw :: DS 'Weak) ->
         case strengthen @(DS 'Strong) dw of
-          Failure{}  -> pure ()
-          Success ds ->
+          Right ds ->
             strengthen (weaken ds) `shouldSatisfy` tryStrengthenSuccessEq ds
+          Left{}  -> pure ()
