diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+## Version 0.0.1 (2018-12-16)
+
+- Replace `p` submodule with `mismi-p`
+- Upgrade tests from QuickCheck to hedgehog
+- Remove `amazonka`, `x`, `disorder` and `twine` submodules
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright 2017, Nick Hibberd <nhibberd@gmail.com>, All Rights Reserved.
+
+Copyright 2017, Ambiata, All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+
+  2. 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.
+
+  3. 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/mismi-core.cabal b/mismi-core.cabal
new file mode 100644
--- /dev/null
+++ b/mismi-core.cabal
@@ -0,0 +1,127 @@
+version: 0.0.1
+
+name:
+  mismi-core
+author:
+  Nick Hibberd
+maintainer:
+  Nick Hibberd <nhibberd@gmail.com>
+homepage:
+  https://github.com/nhibberd/mismi
+bug-reports:
+  https://github.com/nhibberd/mismi/issues
+synopsis:
+  AWS Library
+description:
+  mismi-core is a library designed on top of amazonka and
+  amazonka-core that aims to provide useful functions around safety,
+  functionality and robustness, for example: retrying, bracketing,
+  enabling debugging and runner functions with error handling.
+category:
+  AWS
+license:
+  BSD3
+license-file:
+  LICENSE
+cabal-version:
+  >= 1.8
+build-type:
+  Simple
+tested-with:
+    GHC == 8.2.2
+  , GHC == 8.4.3
+extra-source-files:
+  CHANGELOG.md
+
+library
+  build-depends:
+      base                            >= 3          && < 5
+    , mismi-kernel
+    , mismi-p
+    , amazonka                        >= 1.5        && < 1.7
+    , amazonka-core                   >= 1.5        && < 1.7
+    , bytestring                      >= 0.10       && < 0.11
+    , exceptions                      >= 0.7        && < 0.11
+    , http-client                     >= 0.4.18     && < 0.6
+    , http-types                      >= 0.9        && < 0.13
+    , lens                            >= 4.8        && < 4.17
+    , mtl                             >= 2.1        && < 2.3
+    , resourcet                       >= 1.1        && < 1.3
+    , retry                           == 0.7.*
+    , text                            >= 1.1        && < 1.3
+    , transformers                    >= 0.3.1      && < 0.6
+
+  ghc-options:
+    -Wall
+
+  hs-source-dirs:
+    src
+
+
+  exposed-modules:
+    Mismi
+    Mismi.Amazonka
+    Mismi.Control
+    Mismi.Data
+    Mismi.Environment
+
+
+test-suite test
+  type:
+    exitcode-stdio-1.0
+
+  main-is:
+    test.hs
+
+  ghc-options:
+    -Wall -threaded -O2
+
+  hs-source-dirs:
+    test
+
+  other-modules:
+    Test.Mismi
+    Test.Mismi.Data
+    Test.Mismi.Gen
+
+
+  build-depends:
+      base
+    , mismi-core
+    , mismi-p
+    , mmorph                          >= 1.0        && < 1.2
+    , amazonka-core                   >= 1.5        && < 1.7
+    , hedgehog
+    , transformers                    >= 0.3.1      && < 0.6
+
+
+test-suite test-io
+  type:
+    exitcode-stdio-1.0
+
+  main-is:
+    test-io.hs
+
+  ghc-options:
+    -Wall -threaded -O2
+
+  hs-source-dirs:
+    test
+
+  other-modules:
+    Test.Mismi
+    Test.Mismi.Data
+    Test.Mismi.Gen
+    Test.IO.Mismi.Control
+
+  build-depends:
+      base
+    , mismi-core
+    , mismi-core-test
+    , mismi-p
+    , mmorph                          >= 1.0        && < 1.2
+    , amazonka-core                   >= 1.5        && < 1.7
+    , hedgehog
+    , exceptions                      >= 0.7        && < 0.11
+    , resourcet                       >= 1.1        && < 1.3
+    , transformers                    >= 0.3.1      && < 0.6
diff --git a/src/Mismi.hs b/src/Mismi.hs
new file mode 100644
--- /dev/null
+++ b/src/Mismi.hs
@@ -0,0 +1,7 @@
+module Mismi (
+    module X
+  ) where
+
+import           Mismi.Control as X
+import           Mismi.Data as X
+import           Mismi.Environment as X
diff --git a/src/Mismi/Amazonka.hs b/src/Mismi/Amazonka.hs
new file mode 100644
--- /dev/null
+++ b/src/Mismi/Amazonka.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+module Mismi.Amazonka (
+    module A
+  ) where
+
+import           Network.AWS as A
+import           Network.AWS.Auth as A
+import           Network.AWS.Data as A
+import           Network.AWS.Error as A
+import           Network.AWS.Waiter as A
+#if MIN_VERSION_amazonka(1,4,0)
+import           Network.AWS.Prelude as A
+#else
+import           Network.AWS.Prelude as A hiding ((&))
+#endif
diff --git a/src/Mismi/Control.hs b/src/Mismi/Control.hs
new file mode 100644
--- /dev/null
+++ b/src/Mismi/Control.hs
@@ -0,0 +1,355 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DoAndIfThenElse #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module Mismi.Control (
+    A.AWS
+  , A.Error
+  , A.AccessKey
+  , A.SecretKey
+  , A.SessionToken
+  , A.Region (..)
+  , runAWS
+  , runAWST
+  , runAWSTWith
+  , runAWSTWithRegion
+  , rawRunAWS
+  , runAWSWithRegion
+  , newEnvFromCreds
+  , awsBracket
+  , awsBracket_
+  , unsafeRunAWS
+  , renderError
+  , onStatus
+  , onStatus_
+  , handle404
+  , handle403
+  , handle301
+  , setServiceRetry
+  , setRetry
+  , configureRetries
+  , handleServiceError
+  , withRetries
+  , withRetriesOf
+  , throwOrRetry
+  , throwOrRetryOf
+  ) where
+
+import           Control.Exception (IOException)
+import           Control.Lens (over, (.~), (^.), (^?))
+import           Control.Monad.Catch (Handler (..), MonadCatch, MonadMask, SomeException, bracket, catch, fromException, throwM, try)
+import           Control.Monad.IO.Class (MonadIO, liftIO)
+import           Control.Monad.Reader (ask, local)
+import           Control.Monad.Trans.Except (ExceptT (..), mapExceptT, runExceptT)
+import           Control.Retry (RetryPolicyM, RetryStatus)
+import           Control.Retry (applyPolicy, fullJitterBackoff, recovering, rsIterNumber)
+
+import           Data.ByteString.Builder (toLazyByteString)
+import qualified Data.ByteString.Lazy as BL
+import           Data.Text.Encoding (decodeUtf8)
+
+import           Mismi.Environment (discoverAWSEnvWithRegion)
+
+import           Network.AWS hiding (runAWS)
+import qualified Network.AWS as A
+import           Network.AWS.Data
+import           Network.AWS.Error
+
+import           Network.HTTP.Client (HttpException (..))
+#if MIN_VERSION_http_client(0,5,0)
+import           Network.HTTP.Client (HttpExceptionContent (..), responseStatus, responseTimeoutMicro)
+#endif
+import           Network.HTTP.Client.Internal (mResponseTimeout)
+import           Network.HTTP.Types.Status (Status (..))
+import qualified Network.HTTP.Types as HTTP
+
+import           P
+
+runAWST :: Env -> (Error -> e) -> ExceptT e AWS a -> ExceptT e IO a
+runAWST e err action =
+  runAWSTWith (runAWS e) err action
+
+runAWSTWithRegion :: Region -> (Error -> e) -> ExceptT e AWS a -> ExceptT e IO a
+runAWSTWithRegion r err action =
+  runAWSTWith (runAWSWithRegion r) err action
+
+runAWSTWith :: (forall b. AWS b -> ExceptT Error IO b) -> (Error -> e) -> ExceptT e AWS a -> ExceptT e IO a
+runAWSTWith run err action =
+  joinErrors id err $ mapExceptT run action
+
+joinErrors :: (Functor m, Monad m) => (x -> z) -> (y -> z) -> ExceptT x (ExceptT y m) a -> ExceptT z m a
+joinErrors f g =
+  let
+    first' h =
+      either (Left . h) Right
+
+    second' =
+      fmap
+  in
+    mapExceptT (fmap (join . second' (first' f) . first' g)) . runExceptT
+
+runAWS :: (MonadIO m, MonadCatch m) => Env -> AWS a -> ExceptT Error m a
+runAWS e'' =
+  let
+    e' = over envManager (\m -> m { mResponseTimeout =
+#if MIN_VERSION_http_client(0,5,0)
+        responseTimeoutMicro 60000000
+#else
+        Just 60000000
+#endif
+      }) e''
+    e = configureRetries 5 e'
+  in
+    ExceptT . try . liftIO . rawRunAWS e
+
+runAWSWithRegion :: (MonadIO m, MonadCatch m) => Region -> AWS a -> ExceptT Error m a
+runAWSWithRegion r a = do
+  e <- liftIO $ discoverAWSEnvWithRegion r
+  runAWS e a
+
+newEnvFromCreds :: (Applicative m, MonadIO m, MonadCatch m) => Region -> AccessKey -> SecretKey -> Maybe SessionToken -> m Env
+newEnvFromCreds r ak sk st = do
+#if MIN_VERSION_amazonka(1,4,4)
+  e <- newEnv $ case st of
+    Nothing ->
+      FromKeys ak sk
+    Just st' ->
+      FromSession ak sk st'
+  pure $ e & envRegion .~ r
+#else
+  newEnv r $ case st of
+    Nothing ->
+      FromKeys ak sk
+    Just st' ->
+      FromSession ak sk st'
+#endif
+
+rawRunAWS :: Env -> AWS a -> IO a
+rawRunAWS e =
+  runResourceT . A.runAWS e
+
+awsBracket :: AWS a -> (a -> AWS c) -> (a -> AWS b) -> AWS b
+awsBracket r f a = do
+  e <- ask
+  liftIO $ bracket (unsafeRunAWS e r) (unsafeRunAWS e . f) (unsafeRunAWS e . a)
+
+awsBracket_ :: AWS a -> AWS c -> AWS b -> AWS b
+awsBracket_ r f a =
+  awsBracket r (const f) (const a)
+
+unsafeRunAWS :: Env -> AWS a -> IO a
+unsafeRunAWS e a =
+  either throwM pure =<< runExceptT (runAWS e a)
+
+renderError :: Error -> Text
+renderError =
+  decodeUtf8 . BL.toStrict . toLazyByteString . build
+
+setServiceRetry :: Retry -> AWS a -> AWS a
+setServiceRetry r =
+  local (override (serviceRetry .~ r))
+
+setRetry :: Int -> AWS a -> AWS a
+setRetry =
+  local . configureRetries
+
+withRetries :: (MonadCatch m, MonadMask m, MonadIO m) => Int -> m a -> m a
+withRetries =
+  withRetriesOf (fullJitterBackoff 500000)
+
+withRetriesOf :: (MonadCatch m, MonadMask m, MonadIO m) => RetryPolicyM m -> Int -> m a -> m a
+withRetriesOf policy n action = do
+  recovering policy [httpCondition n, ioCondition n] $ \_ ->
+    action
+
+httpCondition :: Applicative m => Int -> RetryStatus -> Handler m Bool
+httpCondition n s =
+  Handler $ \(e :: HttpException) ->
+    pure $
+      if rsIterNumber s > n
+        then False
+        else checkException e False
+
+ioCondition :: Applicative m => Int -> RetryStatus -> Handler m Bool
+ioCondition n s =
+  Handler $ \(_ :: IOException) ->
+    pure $ rsIterNumber s < n
+
+throwOrRetry ::
+     (MonadCatch m, MonadMask m, MonadIO m)
+   => Int
+   -> SomeException
+   -> RetryStatus
+   -> m RetryStatus
+throwOrRetry =
+  throwOrRetryOf (fullJitterBackoff 500000)
+
+throwOrRetryOf ::
+     (MonadCatch m, MonadMask m, MonadIO m)
+   => RetryPolicyM m
+   -> Int
+   -> SomeException
+   -> RetryStatus
+   -> m RetryStatus
+throwOrRetryOf policy n ex0 s0 =
+  let
+    recover = \case
+      [] ->
+        throwM ex0
+
+      h0 : hs ->
+        case h0 s0 of
+          Handler h ->
+            case fromException ex0 of
+              Nothing ->
+                recover hs
+
+              Just ex -> do
+                ok <- h ex
+                if ok then do
+                  ms <- applyPolicy policy s0
+                  case ms of
+                    Nothing ->
+                      throwM ex
+
+                    Just s ->
+                      pure s
+
+                else
+                  throwM ex
+  in
+    recover [httpCondition n, ioCondition n]
+
+configureRetries :: Int -> Env -> Env
+configureRetries i e = e & envRetryCheck .~ err
+  where
+    err c _ | c >= i = False
+    err c v =
+      checkException v $ (e ^. envRetryCheck) c v
+
+checkException :: HttpException -> Bool -> Bool
+checkException v f =
+  case v of
+#if MIN_VERSION_http_client(0,5,0)
+    InvalidUrlException _ _ ->
+      False
+    HttpExceptionRequest _req content ->
+      case content of
+        NoResponseDataReceived ->
+          True
+        StatusCodeException resp _ ->
+          let status = responseStatus resp in
+          status == HTTP.status500 || status == HTTP.status503
+        ResponseTimeout ->
+          True
+        ConnectionTimeout ->
+          True
+        ConnectionFailure _ ->
+          True
+        ResponseBodyTooShort _ _ ->
+          True
+        InternalException _ ->
+          True
+        InvalidStatusLine _ ->
+          True
+        InvalidHeader _ ->
+          True
+        ProxyConnectException _ _ _ ->
+          True
+        WrongRequestBodyStreamSize _ _ ->
+          True
+        InvalidChunkHeaders ->
+          True
+        IncompleteHeaders ->
+          True
+        HttpZlibException _ ->
+          True
+
+        TooManyRedirects _ ->
+          False
+        OverlongHeaders ->
+          False
+        TlsNotSupported ->
+          False
+        InvalidDestinationHost _ ->
+          False
+        InvalidProxyEnvironmentVariable _ _ ->
+          False
+
+        _ ->
+          f
+#else
+    NoResponseDataReceived ->
+      True
+    StatusCodeException status _ _ ->
+      status == HTTP.status500 || status == HTTP.status503
+    FailedConnectionException _ _ ->
+      True
+    FailedConnectionException2 _ _ _ _ ->
+      True
+    TlsException _ ->
+      True
+    InternalIOException _ ->
+      True
+    HandshakeFailed ->
+      True
+    ResponseTimeout ->
+      True
+    ResponseBodyTooShort _ _ ->
+      True
+    _ ->
+      f
+#endif
+
+handle404 :: AWS a -> AWS (Maybe a)
+handle404 =
+  handleStatus HTTP.status404
+
+handle403 :: AWS a -> AWS (Maybe a)
+handle403 =
+  handleStatus HTTP.status403
+
+handle301 :: AWS a -> AWS (Maybe a)
+handle301 =
+  handleStatus HTTP.status301
+
+handleStatus :: Status -> AWS a -> AWS (Maybe a)
+handleStatus s m =
+  fmap Just m `catch` \(e :: Error) ->
+    if e ^? httpStatus == Just s then return Nothing else throwM e
+
+-- | Return a result code depending on the HTTP status
+onStatus :: (Status -> Maybe r) -> AWS a -> AWS (Either r a)
+onStatus f m =
+  fmap Right m `catch` \(e :: Error) ->
+    case e ^? httpStatus >>= f of
+      Just r1 ->
+        return (Left r1)
+      Nothing ->
+        throwM e
+
+-- | Return a result code depending on the HTTP status
+--   for an AWS action returning no value
+onStatus_ :: r -> (Status -> Maybe r) -> AWS () -> AWS r
+onStatus_ r f m =
+  fmap (const r) m `catch` \(e :: Error) ->
+    case e ^? httpStatus >>= f of
+      Just r1 ->
+        return r1
+      Nothing ->
+        throwM e
+
+handleServiceError :: (ServiceError -> Bool) -> (ServiceError -> a) -> AWS a -> AWS a
+handleServiceError f pass action =
+  action `catch` \(e :: Error) ->
+    case e of
+      ServiceError se ->
+        if f se
+          then pure $ pass se
+          else throwM e
+      SerializeError _ ->
+        throwM e
+      TransportError _ ->
+        throwM e
diff --git a/src/Mismi/Data.hs b/src/Mismi/Data.hs
new file mode 100644
--- /dev/null
+++ b/src/Mismi/Data.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE CPP #-}
+module Mismi.Data (
+    module Mismi.Kernel.Data
+  , fromMismiRegion
+  , toMismiRegion
+  ) where
+
+import           Control.Monad.Trans.AWS (Region (..))
+
+import           Mismi.Kernel.Data
+
+
+fromMismiRegion :: MismiRegion -> Region
+fromMismiRegion r =
+  case r of
+    IrelandRegion ->
+      Ireland
+    FrankfurtRegion ->
+      Frankfurt
+    TokyoRegion ->
+      Tokyo
+    SingaporeRegion ->
+      Singapore
+    SydneyRegion ->
+      Sydney
+    BeijingRegion ->
+      Beijing
+    NorthVirginiaRegion ->
+      NorthVirginia
+    NorthCaliforniaRegion ->
+      NorthCalifornia
+    OregonRegion ->
+      Oregon
+    GovCloudRegion ->
+      GovCloud
+    GovCloudFIPSRegion ->
+      GovCloudFIPS
+    SaoPauloRegion ->
+      SaoPaulo
+    OhioRegion ->
+      Ohio
+    SeoulRegion ->
+      Seoul
+    MumbaiRegion ->
+      Mumbai
+    MontrealRegion ->
+      Montreal
+    LondonRegion ->
+      London
+
+toMismiRegion :: Region -> MismiRegion
+toMismiRegion r =
+  case r of
+    Ireland ->
+      IrelandRegion
+    Frankfurt ->
+      FrankfurtRegion
+    Tokyo ->
+      TokyoRegion
+    Singapore ->
+      SingaporeRegion
+    Sydney ->
+      SydneyRegion
+    Beijing ->
+      BeijingRegion
+    NorthVirginia ->
+      NorthVirginiaRegion
+    NorthCalifornia ->
+      NorthCaliforniaRegion
+    Oregon ->
+      OregonRegion
+    GovCloud ->
+      GovCloudRegion
+    GovCloudFIPS ->
+      GovCloudFIPSRegion
+    SaoPaulo ->
+      SaoPauloRegion
+    Ohio ->
+      OhioRegion
+    Seoul ->
+      SeoulRegion
+    Mumbai ->
+      MumbaiRegion
+    Montreal ->
+      MontrealRegion
+    London ->
+      LondonRegion
diff --git a/src/Mismi/Environment.hs b/src/Mismi/Environment.hs
new file mode 100644
--- /dev/null
+++ b/src/Mismi/Environment.hs
@@ -0,0 +1,131 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Mismi.Environment (
+    Env
+  , Region (..)
+  , RegionError (..)
+  , Debugging (..)
+  , getRegionFromEnv
+  , getDebugging
+  , setDebugging
+  , renderRegionError
+  , discoverAWSEnv
+  , discoverAWSEnvWithRegion
+  , discoverAWSEnvRetry
+  , discoverAWSEnvWithRegionRetry
+  , catchAuthError
+  , newMismiEnv
+  ) where
+
+import           Control.Lens ((.~))
+import           Control.Monad.Catch (Handler (..), MonadCatch (..), MonadThrow (..))
+import           Control.Monad.IO.Class (MonadIO (..))
+import           Control.Monad.Trans.AWS (Credentials (..), Region (..))
+import           Control.Monad.Trans.AWS (Env, envLogger, envRegion, newEnv)
+import           Control.Monad.Trans.AWS (LogLevel (..), Logger, newLogger)
+import           Control.Monad.Trans.Class (lift)
+import           Control.Monad.Trans.Except (ExceptT, throwE)
+import           Control.Retry (RetryPolicyM, constantDelay, limitRetries, recovering)
+
+import qualified Data.Text as T
+import           Data.Typeable (Typeable)
+
+import           Network.AWS.Auth (AuthError (..))
+import           Network.AWS.Data (fromText)
+
+import           P
+
+import           System.Environment (lookupEnv)
+import           System.IO (IO, stderr)
+
+data RegionError =
+    MissingRegion
+  | UnknownRegion Text
+  deriving (Eq, Show, Typeable)
+
+data Debugging =
+    DebugEnabled Logger
+  | DebugDisabled
+
+
+getRegionFromEnv :: (MonadIO m, MonadThrow m) => ExceptT RegionError m Region
+getRegionFromEnv = do
+  mr <- liftIO $ lookupEnv "AWS_DEFAULT_REGION"
+  case mr of
+    Nothing ->
+      throwE MissingRegion
+    Just a ->
+      case fromText $ T.pack a of
+        Left e ->
+          throwE $ UnknownRegion (T.pack e)
+        Right r ->
+          pure r
+
+getDebugging :: MonadIO m => m Debugging
+getDebugging = do
+  d <- liftIO $ lookupEnv "AWS_DEBUG"
+  maybe
+    (return DebugDisabled)
+    (\s ->
+      case T.pack s of
+        "true" ->
+          return . DebugEnabled =<< newLogger Trace stderr
+        "1" ->
+          return . DebugEnabled =<< newLogger Trace stderr
+        _ ->
+          return DebugDisabled)
+    d
+
+setDebugging :: Debugging -> Env -> Env
+setDebugging d e =
+  case d of
+    DebugEnabled lgr ->
+      e & envLogger .~ lgr
+    DebugDisabled ->
+      e
+
+discoverAWSEnv :: ExceptT RegionError IO Env
+discoverAWSEnv =
+  discoverAWSEnvRetry $ limitRetries 1 <> constantDelay 200000
+
+discoverAWSEnvWithRegion :: Region -> IO Env
+discoverAWSEnvWithRegion r =
+  flip discoverAWSEnvWithRegionRetry r $ limitRetries 1 <> constantDelay 200000
+
+discoverAWSEnvRetry :: RetryPolicyM IO -> ExceptT RegionError IO Env
+discoverAWSEnvRetry retry = do
+  r <- getRegionFromEnv
+  lift $ discoverAWSEnvWithRegionRetry retry r
+
+discoverAWSEnvWithRegionRetry :: RetryPolicyM IO -> Region -> IO Env
+discoverAWSEnvWithRegionRetry rpol r = do
+  d <- getDebugging
+  e <- recovering rpol [(\_ -> Handler catchAuthError)] $ \_ -> newMismiEnv r Discover
+  pure $ setDebugging d e
+
+
+newMismiEnv :: (Applicative m, MonadIO m, MonadCatch m) => Region -> Credentials -> m Env
+newMismiEnv r c = do
+  e <- newEnv c
+  pure $ e & envRegion .~ r
+
+catchAuthError :: AuthError -> IO Bool
+-- MDS sometimes has transient failures.
+catchAuthError (RetrievalError _)   = pure True
+-- 'MissingFileError' is rethrown from 'getAuth' in
+-- 'Discover' mode if 'isEC2' (which queries the MDS) returns
+-- 'False'.
+-- FIXME(sio): fix this upstream so we can distinguish between
+-- legit 'MissingFileError's and MDS failures.
+catchAuthError (MissingFileError _) = pure True
+-- Everything else is unlikely to be transient.
+catchAuthError _                    = pure False
+
+renderRegionError :: RegionError -> Text
+renderRegionError e =
+  case e of
+    UnknownRegion r ->
+      "Unknown region: " <> r
+    MissingRegion ->
+      "Environment variable AWS_DEFAULT_REGION was not found"
diff --git a/test/Test/IO/Mismi/Control.hs b/test/Test/IO/Mismi/Control.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/IO/Mismi/Control.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+module Test.IO.Mismi.Control (tests) where
+
+import           Control.Monad.Catch (throwM, catchIOError)
+import           Control.Monad.IO.Class (liftIO)
+import           Control.Monad.Trans.Resource (register)
+
+import           Data.IORef (modifyIORef, newIORef, readIORef)
+
+import           Hedgehog
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+
+import           Mismi.Control (awsBracket)
+
+import           P
+
+import           System.IO.Error (userError)
+
+import           Test.Mismi (testAWS, liftAWS, runAWSDefaultRegion)
+
+prop_bracket :: Property
+prop_bracket =
+  property $ do
+    l <- forAll $ Gen.list (Range.linear 0 100) (Gen.text (Range.linear 0 50) Gen.alphaNum)
+    final <- forAll $ Gen.text (Range.linear 1 10) Gen.alphaNum
+    action <- forAll $ Gen.text (Range.linear 1 10) Gen.alphaNum
+
+    ref <- liftIO $ newIORef l
+    let
+      after' = (flip modifyIORef (final :))
+      action' = (flip modifyIORef (action :))
+    liftIO . runAWSDefaultRegion $ awsBracket (liftIO $ return ref) (liftIO . after') (liftIO . action')
+    result <- liftIO $ readIORef ref
+    result === final : action : l
+
+prop_bracket_catch :: Property
+prop_bracket_catch =
+  property $ do
+    l <- forAll $ Gen.list (Range.linear 0 100) (Gen.text (Range.linear 0 50) Gen.alphaNum)
+    final <- forAll $ Gen.text (Range.linear 1 10) Gen.alphaNum
+
+    ref <- liftIO $ newIORef l
+    let
+      after' = (flip modifyIORef (final :))
+      action' = const $ throwM (userError "")
+    liftIO . runAWSDefaultRegion $
+      awsBracket (liftIO $ return ref) (liftIO . after') (liftIO . action') `catchIOError` (const $ return ())
+    result <- liftIO $ readIORef ref
+    result === final : l
+
+
+prop_testAWS :: Property
+prop_testAWS =
+  withTests 1 . testAWS $
+    pure ()
+
+prop_testAWS_PropertyT :: Property
+prop_testAWS_PropertyT =
+  withTests 1 . property . liftAWS $ do
+    False === False
+    True === True
+
+
+prop_finalizer :: Property
+prop_finalizer =
+  property $ do
+    ref <- liftIO $ newIORef (0 :: Int)
+    liftIO . runAWSDefaultRegion $ do
+      void $ register (modifyIORef ref (const $ 1))
+    result <- liftIO $ readIORef ref
+    result === 1
+
+tests :: IO Bool
+tests =
+  checkSequential $$(discover)
diff --git a/test/Test/Mismi.hs b/test/Test/Mismi.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Mismi.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Test.Mismi (
+    testAWS
+  , liftAWS
+  , enableTests
+  , runAWSDefaultRegion
+  ) where
+
+import           Control.Monad.Morph (hoist)
+import           Control.Monad.Trans.Class (lift)
+import           Control.Monad.Trans.Except (runExceptT)
+
+import           Hedgehog
+
+import           Mismi
+import           Mismi.Control (unsafeRunAWS)
+
+import           P
+import           Prelude (String)
+
+import           System.Environment (lookupEnv)
+import           System.IO (IO)
+
+testAWS :: AWS () -> Property
+testAWS action =
+  property $
+    liftAWS (lift action)
+
+liftAWS :: PropertyT AWS a -> PropertyT IO a
+liftAWS =
+  hoist runAWSDefaultRegion
+
+-- Default to Sydney for tests only, production should fail without the environment variable
+runAWSDefaultRegion :: AWS a -> IO a
+runAWSDefaultRegion a = do
+  r <- either (const $ pure Sydney) pure =<< runExceptT getRegionFromEnv
+  e <- discoverAWSEnvWithRegion r
+  unsafeRunAWS e a
+
+-- Environment variable to lookup, tests to run when it is set to
+-- false and tests to run when when it is set to true (or missing).
+enableTests :: String -> [IO Bool]  -> [IO Bool] -> IO [IO Bool]
+enableTests k false true = do
+  d <- lookupEnv k
+  pure $ bool false true $
+    maybe
+      True
+      (\s ->
+        case s of
+          "true" ->
+            True
+          "1" ->
+            True
+          "false" ->
+            False
+          "0" ->
+            False
+          _ ->
+            True)
+      d
diff --git a/test/Test/Mismi/Data.hs b/test/Test/Mismi/Data.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Mismi/Data.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+module Test.Mismi.Data where
+
+import           Hedgehog
+
+import           Mismi.Data
+
+import           P
+
+import qualified Test.Mismi.Gen as Gen
+
+prop_tripping_region :: Property
+prop_tripping_region =
+  property $ do
+    region <- forAll Gen.genRegion
+    region === fromMismiRegion (toMismiRegion region)
+
+tests :: IO Bool
+tests =
+  checkSequential $$(discover)
diff --git a/test/Test/Mismi/Gen.hs b/test/Test/Mismi/Gen.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Mismi/Gen.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Test.Mismi.Gen (
+    genRegion
+  , genAccessKey
+  , genSecretKey
+  , genSessionToken
+  ) where
+
+import           Network.AWS.Types (SessionToken (..), SecretKey (..), AccessKey (..))
+
+import           Mismi.Control (Region (..))
+
+import           Hedgehog
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+
+import           P
+
+genRegion :: Gen Region
+genRegion =
+  -- Shorter list than possible, aws doesn't support all potential Regions.
+  Gen.element [Ireland, Tokyo, Singapore, Sydney, NorthCalifornia, Oregon, NorthVirginia]
+
+genAccessKey :: Gen AccessKey
+genAccessKey =
+  AccessKey <$>
+    Gen.utf8 (Range.linear 0 50) Gen.alphaNum
+
+genSecretKey :: Gen SecretKey
+genSecretKey =
+  SecretKey <$>
+    Gen.utf8 (Range.linear 0 50) Gen.alphaNum
+
+genSessionToken :: Gen SessionToken
+genSessionToken =
+  SessionToken <$>
+    Gen.utf8 (Range.linear 0 50) Gen.alphaNum
diff --git a/test/test-io.hs b/test/test-io.hs
new file mode 100644
--- /dev/null
+++ b/test/test-io.hs
@@ -0,0 +1,18 @@
+import           Control.Monad (unless)
+
+import           System.Exit (exitFailure)
+import           System.IO (BufferMode(..), hSetBuffering, stdout, stderr)
+
+import qualified Test.Mismi as Mismi
+import qualified Test.IO.Mismi.Control
+
+main :: IO ()
+main = do
+  hSetBuffering stdout LineBuffering
+  hSetBuffering stderr LineBuffering
+
+  results <- sequence =<< Mismi.enableTests "AWS_TEST" [] [
+      Test.IO.Mismi.Control.tests
+    ]
+
+  unless (and results) exitFailure
diff --git a/test/test.hs b/test/test.hs
new file mode 100644
--- /dev/null
+++ b/test/test.hs
@@ -0,0 +1,17 @@
+import           Control.Monad (unless)
+
+import           System.Exit (exitFailure)
+import           System.IO (BufferMode(..), hSetBuffering, stdout, stderr)
+
+import qualified Test.Mismi.Data
+
+main :: IO ()
+main = do
+  hSetBuffering stdout LineBuffering
+  hSetBuffering stderr LineBuffering
+
+  results <- sequence [
+      Test.Mismi.Data.tests
+    ]
+
+  unless (and results) exitFailure
