diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for railroad
+
+## 0.1.0.0 -- 2026-02-25
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2026, Frederik Kallstrup Mastratisi
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of the copyright holder nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/railroad.cabal b/railroad.cabal
new file mode 100644
--- /dev/null
+++ b/railroad.cabal
@@ -0,0 +1,47 @@
+cabal-version:      3.0
+name:               railroad
+version:            0.1.0.0
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             Frederik Kallstrup Mastratisi
+maintainer:         mastratisi@proton.me
+category:           Control, Error Handling
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+synopsis:           A railway oriented mini-DSL for unified error handling.
+description:
+    A railway oriented mini-DSL for handling and interpreting error states 
+    and cardinality checks across diverse failure types in a unified way.
+homepage:     https://github.com/mastratisi/railroad
+bug-reports:  https://github.com/mastratisi/railroad/issues
+source-repository head
+  type:     git
+  location: https://github.com/mastratisi/railroad
+
+common stuff
+    ghc-options: -Wall
+    build-depends:
+      base ^>=4.20.2.0,
+      effectful >= 2.5.1 && < 2.6,
+      mtl >= 2.3.1 && < 2.4,
+      validation >= 1.1.3 && < 1.2
+
+library
+    import:           stuff
+    exposed-modules:  Railroad
+                      
+    hs-source-dirs:   src
+    default-language: GHC2021
+    
+
+test-suite railroad-test
+    import:           stuff
+    default-language: GHC2021
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          Main.hs
+    other-modules:    RailroadSpec
+    build-depends:
+                  base ^>=4.20.2.0,
+                  hspec  >= 2.11.14 && < 2.12,
+                  railroad
diff --git a/src/Railroad.hs b/src/Railroad.hs
new file mode 100644
--- /dev/null
+++ b/src/Railroad.hs
@@ -0,0 +1,131 @@
+{-# LANGUAGE FlexibleContexts     #-}
+{-# LANGUAGE FlexibleInstances    #-}
+{-# LANGUAGE LambdaCase           #-}
+{-# LANGUAGE TypeFamilies         #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Railroad where
+
+import           Data.Bool               (bool)
+import           Data.Foldable           (toList)
+import           Data.Kind               (Type)
+import           Data.Validation         (Validation, toEither)
+import           Effectful               (Eff, type (:>))
+import           Effectful.Error.Dynamic (Error, throwError_)
+
+
+type family CErr f :: Type where
+  CErr Bool               = ()
+  CErr (Maybe a)          = ()
+  CErr (Either e a)       = e
+  CErr (Validation e a)   = e
+  CErr (t a)              = CErr a
+type family CRes f :: Type where
+  CRes Bool               = ()
+  CRes (Maybe a)          = a
+  CRes (Either e a)       = a
+  CRes (Validation e a)   = a
+  CRes (t a)              = t (CRes a)
+
+-- | A catamorphism to Either
+class Bifurcate f where
+  bifurcate :: f -> Either (CErr f) (CRes f)
+instance Bifurcate Bool where
+  bifurcate = bool (Left ()) (Right ())
+  -- bool   :: b -> b -> Bool -> b
+instance Bifurcate (Maybe a) where
+  bifurcate = maybe (Left ()) Right
+  -- maybe  :: b -> (a -> b) -> Maybe a -> b
+instance Bifurcate (Either e a) where
+  bifurcate = id
+  -- either :: (e -> b) -> (a -> b) -> Either e a -> b
+instance Bifurcate (Validation e a) where
+  bifurcate = toEither
+  -- validation :: (e -> b) -> (a -> b) -> Validation e a -> b
+
+instance (Traversable t, CErr (t Bool) ~ (), CRes (t Bool) ~ t ())
+    => Bifurcate (t Bool) where
+  bifurcate = bifurcate . sequenceA . fmap bifurcate
+  -- Bool is not Applicative, so we need to map to Either first
+instance (Traversable t, CErr (t (Maybe a)) ~ (), CRes (t (Maybe a)) ~ t a)
+    => Bifurcate (t (Maybe a)) where
+  bifurcate = bifurcate . sequenceA
+instance (Traversable t, CErr (t (Either e a)) ~ e, CRes (t (Either e a)) ~ t a)
+    => Bifurcate (t (Either e a)) where
+  bifurcate = sequenceA
+  -- 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
+
+-- | 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)
+collapse toErr = either (throwError_ . toErr) pure . bifurcate
+
+
+-- | Collapses a structure using an error mapper.
+(??) :: forall a es e. (Error e :> es, Bifurcate a) => Eff es a -> (CErr a -> e) -> Eff es (CRes a)
+action ?? toErr = action >>= collapse toErr
+
+-- | Collapses a structure using a constant error.
+(?) :: forall es e a. (Error e :> es, Bifurcate a) => Eff es a -> e -> Eff es (CRes a)
+action ? err = action ?? const err
+
+-- | Collapses any value based on a predicate
+(?>) :: forall es e a. (Error e :> es) => Eff es a -> (a -> Bool) -> (a -> e) -> Eff es a
+(?>) action predicate toErr = do
+  val <- action
+  if predicate val then pure val else throwError_ $ toErr val
+
+-- | Collapses a structure and recovers to a value dependent on the error
+(??~) :: forall es a. Bifurcate a => Eff es a -> (CErr a -> CRes a) -> Eff es (CRes a)
+action ??~ defaultFunc = action >>= either (pure . defaultFunc) pure . bifurcate
+
+-- | Collapses a structure, and recovers to a constant default value in the error case
+(?~) :: forall es a. Bifurcate a => Eff es a -> CRes a -> Eff es (CRes a)
+action ?~ defaultVal = action ??~ (const defaultVal)
+
+
+-- TODO: Derive instances for it? Functor?
+data CardinalityError ta = IsEmpty | TooMany !ta
+
+-- | Catamorphism for CardinalityError
+cardinalityErr :: e -> (ta -> e) -> CardinalityError ta -> e
+cardinalityErr onEmpty onTooMany = \case
+  IsEmpty -> onEmpty
+  TooMany xs -> onTooMany xs
+
+
+-- |  Non-empty is sucess state. Returns the collection as-is.
+(?+) :: forall es e t a. (Error e :> es, Foldable t)
+     => Eff es (t a) -> e -> Eff es (t a)
+(?+) action err = do
+  xs <- action
+  if null xs then throwError_ err else pure xs
+
+
+-- | Single element is sucess state.  Returns the single element
+(?!) :: forall es e t a. (Error e :> es, Foldable t)
+     => Eff es (t a) -> (CardinalityError (t a) -> e) -> Eff es a
+(?!) action toErr = do
+  xs <- action
+  case toList xs of
+    []  -> throwError_ $ toErr IsEmpty
+    [x] -> pure x
+    _   -> throwError_ $ toErr $ TooMany xs
+
+
+-- | Empty is success state. Returns Unit.
+(?∅) :: forall es e t a. (Error e :> es, Foldable t)
+     => Eff es (t a) -> ((t a) -> e) -> Eff es ()
+(?∅) action toErr = do
+  xs <- action
+  if null xs then pure () else throwError_ (toErr xs)
+
+
+
+infixl  0 ??, ?, ?~, ?!, ?+, ?∅
+infixl 1 ?>
+
+
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/RailroadSpec.hs b/test/RailroadSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/RailroadSpec.hs
@@ -0,0 +1,101 @@
+{-# LANGUAGE DataKinds        #-}
+{-# LANGUAGE TypeApplications #-}
+module RailroadSpec where
+
+
+import           Data.Validation
+import           Effectful
+import           Effectful.Error.Dynamic
+import           Railroad
+import           Test.Hspec
+
+-- Helper to run the Railroad effects in a pure context
+runRail :: Eff '[Error String] a -> Either String a
+runRail = runPureEff . runErrorNoCallStack
+
+spec :: Spec
+spec = do
+  describe "Bifurcate Instances" $ do
+    it "bifurcates Bool" $ do
+      bifurcate True  `shouldBe` (Right () :: Either () ())
+      bifurcate False `shouldBe` (Left ()  :: Either () ())
+
+    it "bifurcates Maybe" $ do
+      bifurcate (Just 5) `shouldBe` (Right 5 :: Either () Int)
+      bifurcate (Nothing :: Maybe Int) `shouldBe` (Left () :: Either () Int)
+
+    it "bifurcates Either" $ do
+      bifurcate (Right 5 :: Either String Int) `shouldBe` Right 5
+      bifurcate (Left "fire" :: Either String Int) `shouldBe` Left "fire"
+
+    it "bifurcates Validation" $ do
+      bifurcate (Success 10 :: Validation String Int) `shouldBe` Right 10
+      bifurcate (Failure "ice" :: Validation String Int) `shouldBe` Left "ice"
+
+    it "bifurcates List of Bool" $ do
+        bifurcate [True, True] `shouldBe` (Right [(), ()] :: Either () [()])
+        bifurcate [True, False, True] `shouldBe` (Left () :: Either () [()])
+
+    it "bifurcates List of Maybe" $ do
+      bifurcate [Just 1, Just 2] `shouldBe` (Right [1, 2] :: Either () [Int])
+      bifurcate [Just 1, Nothing] `shouldBe` (Left () :: Either () [Int])
+
+    it "bifurcates List of Either" $ do
+      let input0 = [Right 1, Right 2] :: [Either String Int]
+      bifurcate input0 `shouldBe` Right [1, 2]
+      let input1 = [Right 1, Left "first", Left "second"] :: [Either String Int]
+      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]
+        -- 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"
+
+  describe "Basic Operators (? and ??)" $ do
+    it "unwraps success values with (?)" $ do
+      runRail (pure (Just 10 :: Maybe Int) ? "missing") `shouldBe` Right 10
+
+    it "throws constant error on failure with (?)" $ do
+      runRail (pure (Nothing :: Maybe ()) ? "missing") `shouldBe` Left "missing"
+
+    it "maps internal errors with (??)" $ do
+      let action = pure (Left "original" :: Either String String)
+      runRail (action ?? reverse) `shouldBe` Left "lanigiro"
+
+  describe "Predicate Operator (?>)" $ do
+    it "passes when predicate is met" $ do
+      runRail (pure 10 ?> (> 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"
+
+  describe "Recovery Operators (?~ and ??~)" $ do
+    it "recovers to a constant value with (?~)" $ do
+      runPureEff (pure Nothing ?~ 0) `shouldBe` (0 :: Int)
+
+    it "recovers using a function with (??~)" $ do
+      runPureEff (pure (Left "err") ??~ length) `shouldBe` 3
+
+  describe "Cardinality Operators" $ do
+    describe "(?+)" $ do
+      it "succeeds on non-empty list" $ do
+        runRail (pure [1, 2, 3] ?+ "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
+      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"
+
+    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"
