diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,16 @@
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## 0.3.5.0 - 2024-02-12
+
+### Added
+
+ - `queryPathInfoFromClientCache`
+
+ - Nix support 2.17 up to 2.20
+
+ - A `both` function to combine `Verbosity`
+
 ## 0.3.4.0 - 2023-06-28
 
 ### Added
diff --git a/hercules-ci-cnix-store.cabal b/hercules-ci-cnix-store.cabal
--- a/hercules-ci-cnix-store.cabal
+++ b/hercules-ci-cnix-store.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.4
 
 name:           hercules-ci-cnix-store
-version:        0.3.4.0
+version:        0.3.5.0
 synopsis:       Haskell bindings for Nix's libstore
 category:       Nix
 homepage:       https://docs.hercules-ci.com
@@ -92,8 +92,8 @@
   include-dirs:
       include
   pkgconfig-depends:
-      nix-store >= 2.4 && < 2.17
-    , nix-main >= 2.4 && < 2.17
+      nix-store (>= 2.4 && < 2.19) || (>= 2.19.3 && < 2.21)
+    , nix-main >= 2.4 && < 2.19 || (>= 2.19.3 && < 2.21)
   install-includes:
       hercules-ci-cnix/store.hxx
   hs-source-dirs: src
diff --git a/include/hercules-ci-cnix/store.hxx b/include/hercules-ci-cnix/store.hxx
--- a/include/hercules-ci-cnix/store.hxx
+++ b/include/hercules-ci-cnix/store.hxx
@@ -7,7 +7,12 @@
 
 typedef nix::Strings::iterator StringsIterator;
 typedef nix::DerivationOutputs::iterator DerivationOutputsIterator;
-typedef nix::DerivationInputs::iterator DerivationInputsIterator;
 typedef nix::StringPairs::iterator StringPairsIterator;
 typedef nix::PathSet::iterator PathSetIterator;
 typedef nix::ref<const nix::ValidPathInfo> refValidPathInfo;
+
+#if NIX_IS_AT_LEAST(2,18,0)
+typedef nix::DerivedPathMap<std::set<nix::OutputName>>::Map::iterator DerivationInputsIterator;
+#else
+typedef nix::DerivationInputs::iterator DerivationInputsIterator;
+#endif
diff --git a/src/Hercules/CNix/Encapsulation.hs b/src/Hercules/CNix/Encapsulation.hs
--- a/src/Hercules/CNix/Encapsulation.hs
+++ b/src/Hercules/CNix/Encapsulation.hs
@@ -14,6 +14,6 @@
   -- collectable.
   moveToForeignPtrWrapper :: Ptr a -> IO b
 
-nullableMoveToForeignPtrWrapper :: HasEncapsulation a b => Ptr a -> IO (Maybe b)
+nullableMoveToForeignPtrWrapper :: (HasEncapsulation a b) => Ptr a -> IO (Maybe b)
 nullableMoveToForeignPtrWrapper rawPtr | rawPtr == nullPtr = pure Nothing
 nullableMoveToForeignPtrWrapper rawPtr = Just <$> moveToForeignPtrWrapper rawPtr
diff --git a/src/Hercules/CNix/Std/Set.hs b/src/Hercules/CNix/Std/Set.hs
--- a/src/Hercules/CNix/Std/Set.hs
+++ b/src/Hercules/CNix/Std/Set.hs
@@ -57,7 +57,7 @@
 
 newtype StdSet a = StdSet (ForeignPtr (CStdSet a))
 
-instance HasStdSet a => HasEncapsulation (CStdSet a) (StdSet a) where
+instance (HasStdSet a) => HasEncapsulation (CStdSet a) (StdSet a) where
   moveToForeignPtrWrapper = fmap StdSet . newForeignPtr cDelete
 
 class HasStdSet a where
@@ -67,7 +67,7 @@
   cInsertByPtr :: Ptr a -> Ptr (CStdSet a) -> IO ()
   cCopies :: Ptr (CStdSet a) -> Ptr (Ptr a) -> IO ()
 
-class HasStdSet a => HasStdSetCopyable a where
+class (HasStdSet a) => HasStdSetCopyable a where
   cCopyTo :: Ptr (CStdSet a) -> Ptr a -> IO ()
   cInsert :: a -> Ptr (CStdSet a) -> IO ()
 
@@ -122,20 +122,20 @@
         |]
       |]
 
-new :: forall a. HasStdSet a => IO (StdSet a)
+new :: forall a. (HasStdSet a) => IO (StdSet a)
 new = mask_ $ do
   moveToForeignPtrWrapper =<< cNew @a
 
-size :: HasStdSet a => StdSet a -> IO Int
+size :: (HasStdSet a) => StdSet a -> IO Int
 size (StdSet fptr) = fromIntegral <$> withForeignPtr fptr cSize
 
-fromList :: HasStdSetCopyable a => [a] -> IO (StdSet a)
+fromList :: (HasStdSetCopyable a) => [a] -> IO (StdSet a)
 fromList as = do
   set <- new
   for_ as $ insert set
   pure set
 
-fromListP :: HasStdSet a => [Ptr a] -> IO (StdSet a)
+fromListP :: (HasStdSet a) => [Ptr a] -> IO (StdSet a)
 fromListP as = do
   set <- new
   for_ as $ insertP set
@@ -180,10 +180,10 @@
   ptrs <- toListP vec
   for ptrs moveToForeignPtrWrapper
 
-insert :: HasStdSetCopyable a => StdSet a -> a -> IO ()
+insert :: (HasStdSetCopyable a) => StdSet a -> a -> IO ()
 insert (StdSet fptr) value = withForeignPtr fptr (cInsert value)
 
-insertP :: HasStdSet a => StdSet a -> Ptr a -> IO ()
+insertP :: (HasStdSet a) => StdSet a -> Ptr a -> IO ()
 insertP (StdSet fptr) ptr = withForeignPtr fptr (cInsertByPtr ptr)
 
 insertFP :: (Coercible a' (ForeignPtr a), HasStdSet a) => StdSet a -> a' -> IO ()
diff --git a/src/Hercules/CNix/Std/Vector.hs b/src/Hercules/CNix/Std/Vector.hs
--- a/src/Hercules/CNix/Std/Vector.hs
+++ b/src/Hercules/CNix/Std/Vector.hs
@@ -50,7 +50,7 @@
 
 newtype StdVector a = StdVector (ForeignPtr (CStdVector a))
 
-instance HasStdVector a => HasEncapsulation (CStdVector a) (StdVector a) where
+instance (HasStdVector a) => HasEncapsulation (CStdVector a) (StdVector a) where
   moveToForeignPtrWrapper x = StdVector <$> newForeignPtr cDelete x
 
 class HasStdVector a where
@@ -60,7 +60,7 @@
   cCopies :: Ptr (CStdVector a) -> Ptr (Ptr a) -> IO ()
   cPushBackByPtr :: Ptr a -> Ptr (CStdVector a) -> IO ()
 
-class HasStdVector a => HasStdVectorCopyable a where
+class (HasStdVector a) => HasStdVectorCopyable a where
   cCopyTo :: Ptr (CStdVector a) -> Ptr a -> IO ()
   cPushBack :: a -> Ptr (CStdVector a) -> IO ()
 
@@ -114,12 +114,12 @@
         cPushBack value vec = [CU.exp| void { @VEC(vec)->push_back($(@T() value)) } |]
       |]
 
-new :: forall a. HasStdVector a => IO (StdVector a)
+new :: forall a. (HasStdVector a) => IO (StdVector a)
 new = mask_ $ do
   ptr <- cNew @a
   StdVector <$> newForeignPtr cDelete ptr
 
-size :: HasStdVector a => StdVector a -> IO Int
+size :: (HasStdVector a) => StdVector a -> IO Int
 size (StdVector fptr) = fromIntegral <$> withForeignPtr fptr cSize
 
 toVector :: (HasStdVectorCopyable a, Storable a) => StdVector a -> IO (VS.Vector a)
@@ -131,7 +131,7 @@
       cCopyTo stdVecPtr hsVecPtr
   VS.unsafeFreeze hsVec
 
-toVectorP :: HasStdVector a => StdVector a -> IO (VS.Vector (Ptr a))
+toVectorP :: (HasStdVector a) => StdVector a -> IO (VS.Vector (Ptr a))
 toVectorP stdVec@(StdVector stdVecFPtr) = do
   vecSize <- size stdVec
   hsVec <- VSM.new vecSize
@@ -140,7 +140,7 @@
       cCopies stdVecPtr hsVecPtr
   VS.unsafeFreeze hsVec
 
-fromList :: HasStdVectorCopyable a => [a] -> IO (StdVector a)
+fromList :: (HasStdVectorCopyable a) => [a] -> IO (StdVector a)
 fromList as = do
   vec <- Hercules.CNix.Std.Vector.new
   for_ as $ \a -> pushBack vec a
@@ -161,10 +161,10 @@
 toListFP :: (HasEncapsulation a b, HasStdVector a) => StdVector a -> IO [b]
 toListFP vec = traverse moveToForeignPtrWrapper =<< toListP vec
 
-pushBack :: HasStdVectorCopyable a => StdVector a -> a -> IO ()
+pushBack :: (HasStdVectorCopyable a) => StdVector a -> a -> IO ()
 pushBack (StdVector fptr) value = withForeignPtr fptr (cPushBack value)
 
-pushBackP :: HasStdVector a => StdVector a -> Ptr a -> IO ()
+pushBackP :: (HasStdVector a) => StdVector a -> Ptr a -> IO ()
 pushBackP (StdVector fptr) valueP = withForeignPtr fptr (cPushBackByPtr valueP)
 
 pushBackFP :: (Coercible a' (ForeignPtr a), HasStdVector a) => StdVector a -> a' -> IO ()
diff --git a/src/Hercules/CNix/Store.hs b/src/Hercules/CNix/Store.hs
--- a/src/Hercules/CNix/Store.hs
+++ b/src/Hercules/CNix/Store.hs
@@ -78,8 +78,24 @@
 
 C.include "<nix/path-with-outputs.hh>"
 
+C.include "<nix/hash.hh>"
+
 C.include "hercules-ci-cnix/store.hxx"
 
+#if NIX_IS_AT_LEAST(2,19,0)
+
+C.include "<nix/signals.hh>"
+
+C.include "<nix/hash.hh>"
+
+#endif
+
+#if ! NIX_IS_AT_LEAST(2,20,0)
+
+C.include "<nix/nar-info-disk-cache.hh>"
+
+#endif
+
 C.using "namespace nix"
 
 forNonNull :: Applicative m => Ptr a -> (Ptr a -> m b) -> m (Maybe b)
@@ -456,6 +472,139 @@
                     name = strdup(nameString.c_str());
                     path = nullptr;
                     std::visit(overloaded {
+#if NIX_IS_AT_LEAST(2, 18, 0)
+                      [&](DerivationOutput::InputAddressed doi) -> void {
+                        typ = 0;
+                        path = new StorePath(doi.path);
+                      },
+                      [&](DerivationOutput::CAFixed dof) -> void {
+                        typ = 1;
+                        path = new StorePath(dof.path(store, $fptr-ptr:(Derivation *derivation)->name, nameString));
+                        std::visit(overloaded {
+                          [&](nix::FileIngestionMethod fim_) -> void {
+                            switch (fim_) {
+                              case nix::FileIngestionMethod::Flat:
+                                fim = 0;
+                                break;
+                              case nix::FileIngestionMethod::Recursive:
+                                fim = 1;
+                                break;
+                              default:
+                                fim = -1;
+                                break;
+                            }
+                          },
+                          [&](nix::TextIngestionMethod) -> void {
+                            // FIXME (RFC 92)
+                            fim = -1;
+                          }
+                        }, dof.ca.method.raw);
+
+                        const Hash & hash = dof.ca.hash;
+
+#if NIX_IS_AT_LEAST(2, 20, 0)
+                        switch (hash.algo) {
+                          case HashAlgorithm::MD5:
+                            hashType = 0;
+                            break;
+                          case HashAlgorithm::SHA1:
+                            hashType = 1;
+                            break;
+                          case HashAlgorithm::SHA256:
+                            hashType = 2;
+                            break;
+                          case HashAlgorithm::SHA512:
+                            hashType = 3;
+                            break;
+#else
+                        switch (hash.type) {
+                          case htMD5:
+                            hashType = 0;
+                            break;
+                          case htSHA1:
+                            hashType = 1;
+                            break;
+                          case htSHA256:
+                            hashType = 2;
+                            break;
+                          case htSHA512:
+                            hashType = 3;
+                            break;
+#endif
+                          default:
+                            hashType = -1;
+                            break;
+                        }
+                        hashSize = hash.hashSize;
+                        hashValue = (char*)malloc(hashSize);
+                        std::memcpy((void*)(hashValue),
+                                    (void*)(hash.hash),
+                                    hashSize);
+                      },
+                      [&](DerivationOutput::CAFloating dof) -> void {
+                        typ = 2;
+                        std::visit(overloaded {
+                          [&](nix::FileIngestionMethod fim_) -> void {
+                            switch (fim_) {
+                              case nix::FileIngestionMethod::Flat:
+                                fim = 0;
+                                break;
+                              case nix::FileIngestionMethod::Recursive:
+                                fim = 1;
+                                break;
+                              default:
+                                fim = -1;
+                                break;
+                            }
+                          },
+                          [&](nix::TextIngestionMethod) -> void {
+                            // FIXME (RFC 92)
+                            fim = -1;
+                          }
+                        }, dof.method.raw);
+#if NIX_IS_AT_LEAST(2, 20, 0)
+                        switch (dof.hashAlgo) {
+                          case HashAlgorithm::MD5:
+                            hashType = 0;
+                            break;
+                          case HashAlgorithm::SHA1:
+                            hashType = 1;
+                            break;
+                          case HashAlgorithm::SHA256:
+                            hashType = 2;
+                            break;
+                          case HashAlgorithm::SHA512:
+                            hashType = 3;
+                            break;
+#else
+                        switch (dof.hashType) {
+                          case htMD5:
+                            hashType = 0;
+                            break;
+                          case htSHA1:
+                            hashType = 1;
+                            break;
+                          case htSHA256:
+                            hashType = 2;
+                            break;
+                          case htSHA512:
+                            hashType = 3;
+                            break;
+#endif
+                          default:
+                            hashType = -1;
+                            break;
+                        }
+                      },
+                      [&](DerivationOutput::Deferred) -> void {
+                        typ = 3;
+                      },
+                      [&](DerivationOutput::Impure) -> void {
+                        typ = 4;
+                      },
+                    },
+                    i->second.raw
+#else
                       [&](DerivationOutputInputAddressed doi) -> void {
                         typ = 0;
                         path = new StorePath(doi.path);
@@ -482,7 +631,11 @@
                             // FIXME (RFC 92)
                             fim = -1;
                           }
+#  if NIX_IS_AT_LEAST(2, 17, 0)
+                        }, dof.ca.method.raw);
+#  else
                         }, dof.ca.getMethod().raw);
+#  endif
 #else
                         switch (dof.hash.method) {
                           case nix::FileIngestionMethod::Flat:
@@ -497,7 +650,9 @@
                         }
 #endif
 
-#if NIX_IS_AT_LEAST(2, 16, 0)
+#if NIX_IS_AT_LEAST(2, 17, 0)
+                        const Hash & hash = dof.ca.hash;
+#elif NIX_IS_AT_LEAST(2, 16, 0)
                         const Hash & hash = dof.ca.getHash();
 #else
                         const Hash & hash = dof.hash.hash;
@@ -592,6 +747,7 @@
 #else
                       i->second.output
 #endif
+#endif
                     );
                     i++;
                   }|]
@@ -682,10 +838,48 @@
 getDerivationInputs :: Store -> Derivation -> IO [(StorePath, [ByteString])]
 getDerivationInputs _ = getDerivationInputs'
 
+-- | Get the inputs of a derivation, ignoring dependencies on outputs of outputs (RFC 92 inputs).
 getDerivationInputs' :: Derivation -> IO [(StorePath, [ByteString])]
+#if NIX_IS_AT_LEAST(2, 18, 0)
 getDerivationInputs' derivation =
   bracket
     [C.exp| DerivationInputsIterator* {
+      new DerivationInputsIterator($fptr-ptr:(Derivation *derivation)->inputDrvs.map.begin())
+    }|]
+    deleteDerivationInputsIterator
+    $ \i -> fix $ \continue -> do
+      isEnd <- (0 /=) <$> [C.exp| bool { *$(DerivationInputsIterator *i) == $fptr-ptr:(Derivation *derivation)->inputDrvs.map.end() }|]
+      if isEnd
+        then pure []
+        else do
+          name <-
+            [C.throwBlock| nix::StorePath *{
+              return new StorePath((*$(DerivationInputsIterator *i))->first);
+            }|]
+              >>= moveStorePath
+          outs <-
+            bracket
+              [C.block| Strings* {
+                Strings *r = new Strings();
+
+                for (const auto & i : (*$(DerivationInputsIterator *i))->second.value) {
+                  r->push_back(i);
+                }
+
+                // for (const auto &i : iter->second.childMap) {
+                // TODO (RFC 92)
+                //}
+
+                return r;
+              }|]
+              deleteStrings
+              toByteStrings
+          [C.block| void { (*$(DerivationInputsIterator *i))++; }|]
+          ((name, outs) :) <$> continue
+#else
+getDerivationInputs' derivation =
+  bracket
+    [C.exp| DerivationInputsIterator* {
       new DerivationInputsIterator($fptr-ptr:(Derivation *derivation)->inputDrvs.begin())
     }|]
     deleteDerivationInputsIterator
@@ -712,6 +906,7 @@
               toByteStrings
           [C.block| void { (*$(DerivationInputsIterator *i))++; }|]
           ((name, outs) :) <$> continue
+#endif
 
 deleteDerivationInputsIterator :: Ptr DerivationInputsIterator -> IO ()
 deleteDerivationInputsIterator a = [C.block| void { delete $(DerivationInputsIterator *a); }|]
@@ -857,7 +1052,14 @@
 
     auto info2(*currentInfo);
     info2.sigs.clear();
+#if NIX_IS_AT_LEAST(2, 20, 0)
+    {
+      auto signer = std::make_unique<LocalSigner>(SecretKey { secretKey });
+      info2.sign(*store, *signer);
+    }
+#else
     info2.sign(*store, secretKey);
+#endif
     assert(!info2.sigs.empty());
     auto sig = *info2.sigs.begin();
 
@@ -907,6 +1109,76 @@
     }|]
   newForeignPtr finalizeRefValidPathInfo vpi
 
+-- | Query only the local client cache ("narinfo cache") - does not query the actual store or daemon.
+--
+-- Returns 'Nothing' if nothing is known about the path.
+-- Returns 'Just Nothing' if the path is known to not exist.
+-- Returns 'Just (Just vpi)' if the path is known to exist, with the given 'ValidPathInfo'.
+queryPathInfoFromClientCache ::
+  Store ->
+  StorePath ->
+  IO (Maybe (Maybe (ForeignPtr (Ref ValidPathInfo))))
+queryPathInfoFromClientCache (Store store) (StorePath path) =
+#if NIX_IS_AT_LEAST(2, 20, 0)
+  alloca \isKnownP -> do
+    mvpi <- [C.throwBlock| refValidPathInfo* {
+        ReceiveInterrupts _;
+        Store &store = **$(refStore* store);
+        StorePath &path = *$fptr-ptr:(nix::StorePath *path);
+        bool &isKnown = *$(bool* isKnownP);
+        std::optional<std::shared_ptr<const ValidPathInfo>> maybeVPI =
+            store.queryPathInfoFromClientCache(path);
+        if (!maybeVPI) {
+          isKnown = false;
+          return nullptr;
+        }
+        else {
+          isKnown = true;
+          std::shared_ptr<const ValidPathInfo> &vpi = *maybeVPI;
+          if (vpi)
+            return new refValidPathInfo(vpi);
+          else
+            return nullptr;
+        }
+      }|]
+    isKnown <- peek isKnownP <&> (/= 0)
+    for (guard isKnown) \_ -> do
+      mvpi & traverseNonNull (newForeignPtr finalizeRefValidPathInfo)
+#else
+  alloca \isKnownP -> do
+    mvpi <- [C.throwBlock| refValidPathInfo* {
+        ReceiveInterrupts _;
+        Store &store = **$(refStore* store);
+        StorePath &path = *$fptr-ptr:(nix::StorePath *path);
+        bool &isKnown = *$(bool* isKnownP);
+
+        std::string uri = store.getUri();
+        ref<NarInfoDiskCache> cache = nix::getNarInfoDiskCache();
+
+        // std::pair<nix::NarInfoDiskCache::Outcome, std::shared_ptr<NarInfo>>
+        auto [outcome, maybeNarInfo] =
+          cache->lookupNarInfo(uri, std::string(path.hashPart()));
+
+        if (outcome == nix::NarInfoDiskCache::oValid) {
+          assert(maybeNarInfo);
+          isKnown = true;
+          return new refValidPathInfo(maybeNarInfo);
+        }
+        else if (outcome == nix::NarInfoDiskCache::oInvalid) {
+          isKnown = true;
+          return nullptr;
+        }
+        else {
+          // nix::NarInfoDiskCache::oUnknown or unexpected value
+          isKnown = false;
+          return nullptr;
+        }
+      }|]
+    isKnown <- peek isKnownP <&> (/= 0)
+    for (guard isKnown) \_ -> do
+      mvpi & traverseNonNull (newForeignPtr finalizeRefValidPathInfo)  
+#endif
+
 finalizeRefValidPathInfo :: FinalizerPtr (Ref ValidPathInfo)
 {-# NOINLINE finalizeRefValidPathInfo #-}
 finalizeRefValidPathInfo =
@@ -929,10 +1201,16 @@
 validPathInfoNarHash32 :: ForeignPtr (Ref ValidPathInfo) -> IO ByteString
 validPathInfoNarHash32 vpi =
   unsafePackMallocCString
-    =<< [C.block| const char *{ 
+    =<< [C.block| const char *{
+#if NIX_IS_AT_LEAST(2,20,0)
+      std::string s((*$fptr-ptr:(refValidPathInfo* vpi))->narHash.to_string(nix::HashFormat::Nix32, true));
+#elif NIX_IS_AT_LEAST(2,19,0)
+      std::string s((*$fptr-ptr:(refValidPathInfo* vpi))->narHash.to_string(nix::HashFormat::Base32, true));
+#else
       std::string s((*$fptr-ptr:(refValidPathInfo* vpi))->narHash.to_string(nix::Base32, true));
-      return strdup(s.c_str()); }
-    |]
+#endif
+      return strdup(s.c_str());
+    }|]
 
 -- | Deriver field of a ValidPathInfo struct. Source: store-api.hh
 validPathInfoDeriver :: Store -> ForeignPtr (Ref ValidPathInfo) -> IO (Maybe StorePath)
diff --git a/src/Hercules/CNix/Store/Context.hs b/src/Hercules/CNix/Store/Context.hs
--- a/src/Hercules/CNix/Store/Context.hs
+++ b/src/Hercules/CNix/Store/Context.hs
@@ -66,5 +66,5 @@
             <> M.singleton (C.TypeName "SecretKey") [t|SecretKey|]
       }
 
-unsafeMallocBS :: MonadIO m => IO Foreign.C.String.CString -> m ByteString
+unsafeMallocBS :: (MonadIO m) => IO Foreign.C.String.CString -> m ByteString
 unsafeMallocBS m = liftIO (unsafePackMallocCString =<< m)
diff --git a/src/Hercules/CNix/Util.hs b/src/Hercules/CNix/Util.hs
--- a/src/Hercules/CNix/Util.hs
+++ b/src/Hercules/CNix/Util.hs
@@ -29,6 +29,10 @@
 
 C.include "<nix/util.hh>"
 
+#if NIX_IS_AT_LEAST(2,19,0)
+C.include "<nix/signals.hh>"
+#endif
+
 C.using "namespace nix"
 
 setInterruptThrown :: IO ()
diff --git a/src/Hercules/CNix/Verbosity.hs b/src/Hercules/CNix/Verbosity.hs
--- a/src/Hercules/CNix/Verbosity.hs
+++ b/src/Hercules/CNix/Verbosity.hs
@@ -7,6 +7,7 @@
     getVerbosity,
     setShowTrace,
     getShowTrace,
+    both,
   )
 where
 
@@ -77,3 +78,13 @@
 setShowTrace b =
   let b' = fromBool b
    in [C.throwBlock| void { nix::loggerSettings.showTrace.assign($(bool b')); }|]
+
+-- | Combine two Verbosity values for greater message volume. E.g.
+--
+-- >>> both Warn Notice
+-- Notice
+both :: Verbosity -> Verbosity -> Verbosity
+both =
+  -- Priority ordering is inversely related to message quantity, so this is
+  -- min and not max; see test.
+  max
diff --git a/test/Hercules/CNix/Store/DerivationSpec.hs b/test/Hercules/CNix/Store/DerivationSpec.hs
--- a/test/Hercules/CNix/Store/DerivationSpec.hs
+++ b/test/Hercules/CNix/Store/DerivationSpec.hs
@@ -49,5 +49,5 @@
     os <- getDerivationOutputs store drvName d
     show' os `shouldBe` "[DerivationOutput {derivationOutputName = \"out\", derivationOutputPath = Just 8mygch54p7brcqn83xnfa22ik9y3km95-hello-2.10, derivationOutputDetail = DerivationOutputInputAddressed 8mygch54p7brcqn83xnfa22ik9y3km95-hello-2.10}]"
 
-show' :: Show a => a -> Text
+show' :: (Show a) => a -> Text
 show' = show
diff --git a/test/Hercules/CNix/VerbositySpec.hs b/test/Hercules/CNix/VerbositySpec.hs
--- a/test/Hercules/CNix/VerbositySpec.hs
+++ b/test/Hercules/CNix/VerbositySpec.hs
@@ -22,6 +22,13 @@
           setShowTrace v
           v' <- getShowTrace
           v' `shouldBe` v
+  describe "both" do
+    it "extends the message volume 2nd" do
+      both Warn Notice `shouldBe` Notice
+    it "extends the message volume 1st" do
+      both Warn Error `shouldBe` Warn
+    it "extends the message volume eq" do
+      both Warn Warn `shouldBe` Warn
 
 -- bracket on global state is still a leaky abstraction, so we don't add this
 -- to the library.
