diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for railroad
 
+## 0.2.0.1 -- 2026-10-11
+* Documentation: a README section on the empty-collection gotcha (`?` on a traversable checks that
+  every element succeeds, so an empty one passes; chain `?+` before it), with the quantifiers each
+  operator corresponds to, and a note in the haddocks of `Bifurcate` and `?+`. A test pins it.
+
 ## 0.2.0.0 -- 2026-10-11
 * Breaking: `(?>)` no longer throws. `m ?> p` tags the value (`Right a` if `p a`, else `Left a`,
   carrying the rejected value) and is finished with `?` or `??`: `(m ?> p) toErr` becomes
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.2.0.0
+version:            0.2.0.1
 license:            BSD-3-Clause
 license-file:       LICENSE
 author:             Frederik Kallstrup Mastratisi
diff --git a/readme.md b/readme.md
--- a/readme.md
+++ b/readme.md
@@ -180,7 +180,9 @@
  | `Validation e a` | `Failure`            | `e`                 | `a`                  |
  | `t f`            | Any element is error | `CErr f`            | `t (CRes f)`         |
 
+An empty `t f` has no element to fail, so it succeeds: see [the gotcha](#gotcha-an-empty-collection-passes).
 
+
 For the latter each operator (`?+`, `?!`, `?∅`) interpret what
 cardinalities of a Foldable counts as errors differently. To wit:
 
@@ -248,6 +250,35 @@
 Neither throws by itself, and the right-hand side only runs if the left
 one failed. Unlike `Validation`, which runs all independent checks and
 collects every failure, a fallback chain succeeds on the first good answer.
+
+## Gotcha: an empty collection passes
+
+On a traversable, `?` and `??` check that **every** element succeeds
+(∀). Over an empty collection that holds vacuously, like `and []`:
+
+```haskell
+pure ([] :: [Bool]) ? err403    -- succeeds, with []
+```
+
+That surprises a permission check where "no rows" should mean "not
+permitted". Require at least one element first, with `?+` (∃), and
+chain the two checks:
+
+```haskell
+_ <- runQuery (permitted uid asset) ? err500 ?+ err403 ? err403
+--                                    ^ db error ^ >= 1 row ^ all True
+```
+
+The cardinality operators are the other quantifiers, so the usual
+conditions are each one operator, and they chain:
+
+| Operator                            | Succeeds when                       | Logic                           |
+|-------------------------------------|-------------------------------------|---------------------------------|
+| `?` / `??` on `[Bool]`, `[Maybe a]` | every element succeeds              | ∀ x ∈ xs. succeeds x            |
+| `?+`                                | there is at least one element       | ∃ x ∈ xs                        |
+| `?!`                                | there is exactly one element        | ∃! x ∈ xs                       |
+| `?∅`                                | there is no element                 | ¬∃ x ∈ xs                       |
+| `?+` then `?`                       | there is one, and every one succeeds | (∃ x ∈ xs) ∧ (∀ x ∈ xs. succeeds x) |
 
 ## More
 See [`railroad.md`](railroad.md) for a deeper tour, or just read the
diff --git a/src/Railroad.hs b/src/Railroad.hs
--- a/src/Railroad.hs
+++ b/src/Railroad.hs
@@ -35,6 +35,7 @@
 
 
 -- | Succeeds if non-empty, returning the collection; else throws the constant error.
+-- Chain it before @?@ on a traversable, which an empty collection passes.
 (?+) :: forall es e t a. (Error e :> es, Foldable t)
      => Eff es (t a) -> e -> Eff es (t a)
 (?+) action err = do
diff --git a/src/Railroad/Bifurcate.hs b/src/Railroad/Bifurcate.hs
--- a/src/Railroad/Bifurcate.hs
+++ b/src/Railroad/Bifurcate.hs
@@ -37,6 +37,9 @@
   CRes (t a)              = t (CRes a)
 
 -- | A catamorphism to Either
+--
+-- A traversable fails at its first failing element; an empty one succeeds (use @?+@ to
+-- also require an element).
 class Bifurcate f where
   bifurcate :: f -> Either (CErr f) (CRes f)
 instance Bifurcate Bool where
diff --git a/src/Railroad/MonadError.hs b/src/Railroad/MonadError.hs
--- a/src/Railroad/MonadError.hs
+++ b/src/Railroad/MonadError.hs
@@ -33,6 +33,7 @@
 action ? err = action ?? const err
 
 -- | Succeeds if non-empty, returning the collection; else throws the constant error.
+-- Chain it before @?@ on a traversable, which an empty collection passes.
 (?+) :: forall m e t a. (MonadError e m, Foldable t)
      => m (t a) -> e -> m (t a)
 (?+) action err = do
diff --git a/test/Railroad/MonadErrorSpec.hs b/test/Railroad/MonadErrorSpec.hs
--- a/test/Railroad/MonadErrorSpec.hs
+++ b/test/Railroad/MonadErrorSpec.hs
@@ -36,6 +36,13 @@
       in (runMonadError (pure xs ?∅ (\ys -> "got " ++ show (length ys))) === model)
          .&&. (runMonadError (pure xs ?@ (\ys -> "got " ++ show (length ys))) === model)
 
+  describe "an empty collection passes ? (vacuous truth); ?+ requires an element" $ do
+    it "[] passes ? on [Bool]" $ do
+      runMonadError (pure ([] :: [Bool]) ? "denied") `shouldBe` Right []
+    it "?+ then ? needs at least one element, all succeeding" $ property $ \(bs :: [Bool]) ->
+      runMonadError (pure bs ?+ "none" ? "denied")
+        === (if null bs then Left "none" else if and bs then Right (map (const ()) bs) else Left "denied")
+
   describe "fixity (all operators are infixl 1, like >>=, <&> and &)" $ do
     it "composes with <&> without parentheses" $ do
       runMonadError (pure (Right 2 :: Either String Int) ? "e" <&> (+ 1)) `shouldBe` Right 3
diff --git a/test/RailroadSpec.hs b/test/RailroadSpec.hs
--- a/test/RailroadSpec.hs
+++ b/test/RailroadSpec.hs
@@ -35,6 +35,13 @@
       in (runRail (pure xs ?∅ (\ys -> "got " ++ show (length ys))) === model)
          .&&. (runRail (pure xs ?@ (\ys -> "got " ++ show (length ys))) === model)
 
+  describe "an empty collection passes ? (vacuous truth); ?+ requires an element" $ do
+    it "[] passes ? on [Bool]" $ do
+      runRail (pure ([] :: [Bool]) ? "denied") `shouldBe` Right []
+    it "?+ then ? needs at least one element, all succeeding" $ property $ \(bs :: [Bool]) ->
+      runRail (pure bs ?+ "none" ? "denied")
+        === (if null bs then Left "none" else if and bs then Right (map (const ()) bs) else Left "denied")
+
   describe "fixity (all operators are infixl 1, like >>=, <&> and &)" $ do
     it "composes with <&> without parentheses" $ do
       runRail (pure (Right 2 :: Either String Int) ? "e" <&> (+ 1)) `shouldBe` Right 3
