diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,25 @@
 
 ## [Unreleased]
 
+## [0.6.0.0] - 2026-09-10
+
+### Changed
+
+- Require `effectful >=2.7 && <2.8`.
+- Require `effectful-core >=2.7 && <2.8`.
+- Require `effectful-plugin >=2.2 && <2.3`.
+
+## [0.5.0.0] - 2026-09-09
+
+### Changed
+
+- `Atelier.Time`: Remove orphan `FromJSON` and `ToJSON` instances, and instead
+  introduce `AsRawUnit` and `AsJsonMicrosecond` `newtype`s for `FromJSON` and
+  `ToJSON` instance deriving. This fixes the issue where the time units'
+  `FromJSON` and `ToJSON` instances did not match, meaning
+  `decode (encode (10 :: Hour)) == Nothing` held true. Now you have to be more
+  explicit about how the units will be represented in JSON.
+
 ## [0.4.0.1] - 2026-08-14
 
 ### Removed
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2025 Christian Georgii
+Copyright (c) 2026 Tweag
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,29 +4,14 @@
 
 ## Overview
 
-`atelier-core` provides a set of composable Effectful effects and supporting types for building structured, observable applications.
-
-| Module | Purpose |
-|---|---|
-| `Atelier.Component` | Structured component lifecycle (`setup → listeners → start`) |
-| `Atelier.Config` | Configuration with environment variable overrides |
-| `Atelier.Effects.Log` | Structured logging with hierarchical namespaces |
-| `Atelier.Effects.Conc` | Thread management via [Ki](https://github.com/awkward-squad/ki) (structured concurrency) |
-| `Atelier.Effects.Cache` | Caching with singleflight deduplication |
-| `Atelier.Effects.Publishing` | Event publishing with context propagation |
-| `Atelier.Effects.Monitoring.Tracing` | OpenTelemetry tracing |
-| `Atelier.Effects.Monitoring.Metrics` | Prometheus metrics |
-| `Atelier.Effects.FileWatcher` | Filesystem change notifications |
-| `Atelier.Effects.Process` | External process management |
-
-It also wraps a number of `IO`-based primitives (environment, clock, file system, console, POSIX) as effects so they can be interpreted and tested explicitly: `Atelier.Effects.Env`, `Atelier.Effects.Clock`, `Atelier.Effects.FileSystem`, `Atelier.Effects.Console`, `Atelier.Effects.Posix.*`, and more.
-
-## Part of atelier
+`atelier-core` provides a set of composable Effectful effects and supporting
+types for building structured, observable applications.
 
-- [`atelier-prelude`](https://github.com/tweag/tricorder/tree/main/atelier-prelude) — relude-based prelude with Effectful conventions
-- [`atelier-core`](https://github.com/tweag/tricorder/tree/main/atelier-core) — this package
-- [`atelier-db`](https://github.com/tweag/tricorder/tree/main/atelier-db) — relational database effect (Hasql/Rel8)
-- [`atelier-testing`](https://github.com/tweag/tricorder/tree/main/atelier-testing) — database-backed test utilities
+It also wraps a number of `IO`-based primitives (environment, clock, file
+system, console, POSIX) as effects so they can be interpreted and tested
+explicitly: `Atelier.Effects.Env`, `Atelier.Effects.Clock`,
+`Atelier.Effects.FileSystem`, `Atelier.Effects.Console`,
+`Atelier.Effects.Posix.*`, and more.
 
 ## License
 
diff --git a/atelier-core.cabal b/atelier-core.cabal
--- a/atelier-core.cabal
+++ b/atelier-core.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           atelier-core
-version:        0.4.0.1
+version:        0.6.0.0
 synopsis:       Foundational Effectful-based effects and utilities
 description:    Core effects and utilities for effect-based applications, built on Effectful — part of the atelier toolkit.
 category:       Control
@@ -98,7 +98,7 @@
   ghc-options: -Weverything -Wno-unsafe -Wno-missing-safe-haskell-mode -Wno-monomorphism-restriction -Wno-missing-kind-signatures -Wno-missing-local-signatures -Wno-missing-import-lists -Wno-implicit-prelude -Wno-unticked-promoted-constructors -Wno-unused-packages -Wno-all-missed-specialisations -Wno-missed-specialisations -fplugin=Effectful.Plugin -threaded
   build-depends:
       aeson >=2.2 && <2.4
-    , atelier-prelude >=0.1 && <0.3
+    , atelier-prelude ==0.3.*
     , base >=4.18 && <4.23
     , base64-bytestring ==1.2.*
     , bytestring >=0.11 && <0.13
@@ -107,9 +107,9 @@
     , daemons ==0.4.*
     , data-default ==0.8.*
     , directory ==1.3.*
-    , effectful ==2.6.*
-    , effectful-core ==2.6.*
-    , effectful-plugin >=2.0 && <2.2
+    , effectful ==2.7.*
+    , effectful-core ==2.7.*
+    , effectful-plugin ==2.2.*
     , effectful-th ==1.0.*
     , filepath >=1.4 && <1.6
     , fsnotify ==0.4.*
@@ -190,15 +190,15 @@
       tasty-discover:tasty-discover
   build-depends:
       aeson >=2.2 && <2.4
-    , atelier-core >=0.3 && <0.5
-    , atelier-prelude >=0.1 && <0.3
+    , atelier-core
+    , atelier-prelude ==0.3.*
     , base >=4.18 && <4.23
     , bytestring >=0.11 && <0.13
     , containers >=0.6 && <0.9
     , data-default ==0.8.*
-    , effectful ==2.6.*
-    , effectful-core ==2.6.*
-    , effectful-plugin >=2.0 && <2.2
+    , effectful ==2.7.*
+    , effectful-core ==2.7.*
+    , effectful-plugin ==2.2.*
     , hedgehog ==1.7.*
     , hspec ==2.11.*
     , hspec-hedgehog ==0.3.*
diff --git a/src/Atelier/Config.hs b/src/Atelier/Config.hs
--- a/src/Atelier/Config.hs
+++ b/src/Atelier/Config.hs
@@ -45,7 +45,8 @@
     , extractNestedConfig
     , LoadedConfig (..)
     , runConfig
-    ) where
+    )
+where
 
 import Data.Aeson (FromJSON (..), Value (..))
 import Data.Default (Default (..))
@@ -155,7 +156,8 @@
 -- @
 -- extractNestedConfig \@"foo.bar" \@Text cfg == "test"
 -- @
-extractNestedConfig :: forall (key :: Symbol) r. (Default r, FromJSON r, KnownSymbol key) => LoadedConfig -> r
+extractNestedConfig
+    :: forall (key :: Symbol) r. (Default r, FromJSON r, KnownSymbol key) => LoadedConfig -> r
 extractNestedConfig (LoadedConfig root) = go props root
   where
     props = T.splitOn "." $ toText $ symbolVal $ Proxy @key
diff --git a/src/Atelier/Effects/Arguments.hs b/src/Atelier/Effects/Arguments.hs
--- a/src/Atelier/Effects/Arguments.hs
+++ b/src/Atelier/Effects/Arguments.hs
@@ -14,7 +14,8 @@
 
       -- * Interpreters
     , runArgumentsIO
-    ) where
+    )
+where
 
 import Effectful (Effect, IOE)
 import Effectful.Dispatch.Dynamic (interpret_)
diff --git a/src/Atelier/Effects/Await.hs b/src/Atelier/Effects/Await.hs
--- a/src/Atelier/Effects/Await.hs
+++ b/src/Atelier/Effects/Await.hs
@@ -16,7 +16,8 @@
       -- * Interpreters
     , eachAwait
     , awaitYield
-    ) where
+    )
+where
 
 import Atelier.Effects.Internal.Coroutine
     ( Await
diff --git a/src/Atelier/Effects/Cache.hs b/src/Atelier/Effects/Cache.hs
--- a/src/Atelier/Effects/Cache.hs
+++ b/src/Atelier/Effects/Cache.hs
@@ -14,7 +14,8 @@
     , runCacheTtl
     , runCacheTtlWithWait
     , runCacheForever
-    ) where
+    )
+where
 
 import Data.Time (NominalDiffTime, UTCTime, addUTCTime)
 import Effectful (Effect)
@@ -149,10 +150,11 @@
 evictExpiredEntries store ttl now =
     ListT.fold
         ( \count (k, v) ->
-            if now >= addUTCTime ttl v.createdAt then
-                Map.delete k store $> count + 1
-            else
-                pure count
+            if now >= addUTCTime ttl v.createdAt
+                then
+                    Map.delete k store $> count + 1
+                else
+                    pure count
         )
         0
         $ Map.listT store
diff --git a/src/Atelier/Effects/Cache/Config.hs b/src/Atelier/Effects/Cache/Config.hs
--- a/src/Atelier/Effects/Cache/Config.hs
+++ b/src/Atelier/Effects/Cache/Config.hs
@@ -5,7 +5,8 @@
 -- "Atelier.Effects.Tally".
 module Atelier.Effects.Cache.Config
     ( Config (..)
-    ) where
+    )
+where
 
 import Data.Aeson (FromJSON)
 import Data.Default (Default (..))
diff --git a/src/Atelier/Effects/Cache/Singleflight.hs b/src/Atelier/Effects/Cache/Singleflight.hs
--- a/src/Atelier/Effects/Cache/Singleflight.hs
+++ b/src/Atelier/Effects/Cache/Singleflight.hs
@@ -11,7 +11,8 @@
     , updateCache
     , removeFromCache
     , runSingleflight
-    ) where
+    )
+where
 
 import Effectful (Effect)
 import Effectful.Concurrent (Concurrent)
@@ -88,24 +89,25 @@
                     -- Use tryPutTMVar in case updateCache already filled it
                     filled <- STM.atomically $ STM.tryPutTMVar mvar result
 
-                    if filled then do
-                        -- We successfully filled the TMVar with our result
-                        -- If it was an exception, remove from cache so future requests can retry
-                        case result of
-                            Left _exception -> do
-                                STM.atomically $ Map.delete key cache
-                            Right _ -> pure ()
+                    if filled
+                        then do
+                            -- We successfully filled the TMVar with our result
+                            -- If it was an exception, remove from cache so future requests can retry
+                            case result of
+                                Left _exception -> do
+                                    STM.atomically $ Map.delete key cache
+                                Right _ -> pure ()
 
-                        -- Return the result or re-throw the exception
-                        case result of
-                            Left exception -> throwIO exception
-                            Right value -> pure value
-                    else do
-                        -- updateCache filled it before us - read and use that value
-                        finalResult <- STM.atomically $ STM.readTMVar mvar
-                        case finalResult of
-                            Left exception -> throwIO exception
-                            Right value -> pure value
+                            -- Return the result or re-throw the exception
+                            case result of
+                                Left exception -> throwIO exception
+                                Right value -> pure value
+                        else do
+                            -- updateCache filled it before us - read and use that value
+                            finalResult <- STM.atomically $ STM.readTMVar mvar
+                            case finalResult of
+                                Left exception -> throwIO exception
+                                Right value -> pure value
                 (False, Nothing) -> do
                     result <-
                         STM.atomically
diff --git a/src/Atelier/Effects/Chan.hs b/src/Atelier/Effects/Chan.hs
--- a/src/Atelier/Effects/Chan.hs
+++ b/src/Atelier/Effects/Chan.hs
@@ -40,7 +40,8 @@
     , InChan
     , OutChan
     , readChanBatched
-    ) where
+    )
+where
 
 import Control.Concurrent.Chan.Unagi (InChan, OutChan)
 import Data.Time.Units (TimeUnit, toMicroseconds)
@@ -57,6 +58,8 @@
 
 
 type instance DispatchOf Chan = Static WithSideEffects
+
+
 data instance StaticRep Chan = Chan
 
 
diff --git a/src/Atelier/Effects/Clock.hs b/src/Atelier/Effects/Clock.hs
--- a/src/Atelier/Effects/Clock.hs
+++ b/src/Atelier/Effects/Clock.hs
@@ -13,7 +13,8 @@
     , runClockConst
     , runClockState
     , runClockList
-    ) where
+    )
+where
 
 import Data.Time (UTCTime, getCurrentTime)
 import Data.Time.LocalTime (TimeZone, getCurrentTimeZone, utc)
diff --git a/src/Atelier/Effects/Console.hs b/src/Atelier/Effects/Console.hs
--- a/src/Atelier/Effects/Console.hs
+++ b/src/Atelier/Effects/Console.hs
@@ -15,7 +15,8 @@
     , runConsoleHandle
     , runConsole
     , runConsoleToList
-    ) where
+    )
+where
 
 import Effectful (Effect, IOE)
 import Effectful.Dispatch.Dynamic (interpret_, reinterpret_)
diff --git a/src/Atelier/Effects/Debounce.hs b/src/Atelier/Effects/Debounce.hs
--- a/src/Atelier/Effects/Debounce.hs
+++ b/src/Atelier/Effects/Debounce.hs
@@ -38,7 +38,8 @@
     , ensureEntry
     , ensureCallback
     , Entry (..)
-    ) where
+    )
+where
 
 import Data.Dynamic (Dynamic, fromDynamic, toDyn)
 import Effectful (Effect, Limit (..), Persistence (..), UnliftStrategy (..))
diff --git a/src/Atelier/Effects/Delay.hs b/src/Atelier/Effects/Delay.hs
--- a/src/Atelier/Effects/Delay.hs
+++ b/src/Atelier/Effects/Delay.hs
@@ -18,7 +18,8 @@
     , every
     , runDelay
     , runDelayNoOp
-    ) where
+    )
+where
 
 import Data.Time.Units (TimeUnit, toMicroseconds)
 import Effectful (Effect)
diff --git a/src/Atelier/Effects/Env.hs b/src/Atelier/Effects/Env.hs
--- a/src/Atelier/Effects/Env.hs
+++ b/src/Atelier/Effects/Env.hs
@@ -8,7 +8,8 @@
     , lookupEnv
     , runEnv
     , runEnvConst
-    ) where
+    )
+where
 
 import Effectful (Effect, IOE)
 import Effectful.Dispatch.Dynamic (interpret_)
diff --git a/src/Atelier/Effects/Exit.hs b/src/Atelier/Effects/Exit.hs
--- a/src/Atelier/Effects/Exit.hs
+++ b/src/Atelier/Effects/Exit.hs
@@ -12,7 +12,8 @@
       -- * Interpreters
     , runExit
     , runExitNoOp
-    ) where
+    )
+where
 
 import Effectful (Effect, IOE)
 import Effectful.Dispatch.Dynamic (interpret_, reinterpret_)
diff --git a/src/Atelier/Effects/File.hs b/src/Atelier/Effects/File.hs
--- a/src/Atelier/Effects/File.hs
+++ b/src/Atelier/Effects/File.hs
@@ -22,7 +22,8 @@
     , hIsEOF
     , hSetBuffering
     , runFile
-    ) where
+    )
+where
 
 import Effectful (Dispatch (..), DispatchOf, Effect, IOE)
 import Effectful.Dispatch.Static
@@ -45,6 +46,8 @@
 
 
 type instance DispatchOf File = Static WithSideEffects
+
+
 data instance StaticRep File = File
 
 
diff --git a/src/Atelier/Effects/FileSystem.hs b/src/Atelier/Effects/FileSystem.hs
--- a/src/Atelier/Effects/FileSystem.hs
+++ b/src/Atelier/Effects/FileSystem.hs
@@ -24,7 +24,8 @@
     , runFileSystemIO
     , runFileSystemNoOp
     , runFileSystemState
-    ) where
+    )
+where
 
 import Control.Exception (bracket)
 import Effectful (Effect, IOE)
diff --git a/src/Atelier/Effects/FileWatcher.hs b/src/Atelier/Effects/FileWatcher.hs
--- a/src/Atelier/Effects/FileWatcher.hs
+++ b/src/Atelier/Effects/FileWatcher.hs
@@ -65,7 +65,8 @@
       -- * Internals (exported for testing)
     , deduplicateDirs
     , matchesAny
-    ) where
+    )
+where
 
 import Control.Concurrent (threadDelay)
 import Control.Concurrent.STM (retry)
@@ -204,7 +205,8 @@
 -- Delivers all scripted events to the callback in order, then blocks
 -- indefinitely — matching the blocking semantics of 'runFileWatcherIO'.
 -- The 'Watch' specification is ignored; the caller controls what events are fed in.
-runFileWatcherScripted :: (Concurrent :> es) => [(FilePath, FileEvent)] -> Eff (FileWatcher : es) a -> Eff es a
+runFileWatcherScripted
+    :: (Concurrent :> es) => [(FilePath, FileEvent)] -> Eff (FileWatcher : es) a -> Eff es a
 runFileWatcherScripted events = reinterpret (evalState events) \env -> \case
     WatchFilePaths _ callback ->
         localSeqUnlift env \unlift -> do
diff --git a/src/Atelier/Effects/Input.hs b/src/Atelier/Effects/Input.hs
--- a/src/Atelier/Effects/Input.hs
+++ b/src/Atelier/Effects/Input.hs
@@ -10,7 +10,8 @@
     , runInputConst
     , toReader
     , fromState
-    ) where
+    )
+where
 
 import Effectful (Effect)
 import Effectful.Dispatch.Dynamic (interpret_)
diff --git a/src/Atelier/Effects/Internal/Coroutine.hs b/src/Atelier/Effects/Internal/Coroutine.hs
--- a/src/Atelier/Effects/Internal/Coroutine.hs
+++ b/src/Atelier/Effects/Internal/Coroutine.hs
@@ -42,7 +42,8 @@
 
       -- ** Interpreters
     , awaitYield
-    ) where
+    )
+where
 
 import Effectful (Effect, UnliftStrategy (..), inject, raiseWith)
 import Effectful.Dispatch.Dynamic (interpretWith_, interpret_, reinterpretWith_, reinterpret_)
diff --git a/src/Atelier/Effects/Iterator.hs b/src/Atelier/Effects/Iterator.hs
--- a/src/Atelier/Effects/Iterator.hs
+++ b/src/Atelier/Effects/Iterator.hs
@@ -10,7 +10,8 @@
     , fromEvents
     , filter
     , changes
-    ) where
+    )
+where
 
 import Effectful.Concurrent (Concurrent)
 import Prelude hiding (filter)
diff --git a/src/Atelier/Effects/Log.hs b/src/Atelier/Effects/Log.hs
--- a/src/Atelier/Effects/Log.hs
+++ b/src/Atelier/Effects/Log.hs
@@ -41,7 +41,8 @@
     , runLogNoOp
     , runLogToHandle
     , runLogWriter
-    ) where
+    )
+where
 
 import Data.Aeson (FromJSON (..))
 import Data.Default (Default (..))
diff --git a/src/Atelier/Effects/Posix/Daemons.hs b/src/Atelier/Effects/Posix/Daemons.hs
--- a/src/Atelier/Effects/Posix/Daemons.hs
+++ b/src/Atelier/Effects/Posix/Daemons.hs
@@ -10,7 +10,8 @@
     , isRunning
     , forceKillAndWait
     , runDaemons
-    ) where
+    )
+where
 
 import Data.Default (def)
 import Effectful (Effect, IOE, Limit (..), Persistence (..), UnliftStrategy (..))
diff --git a/src/Atelier/Effects/Posix/IO.hs b/src/Atelier/Effects/Posix/IO.hs
--- a/src/Atelier/Effects/Posix/IO.hs
+++ b/src/Atelier/Effects/Posix/IO.hs
@@ -19,7 +19,8 @@
 module Atelier.Effects.Posix.IO
     ( readFdAll
     , readFdFrom
-    ) where
+    )
+where
 
 import Foreign.Marshal.Alloc (allocaBytes)
 import Foreign.Ptr (castPtr)
@@ -55,10 +56,11 @@
         chunk <- allocaBytes chunkSize \ptr -> do
             n <- Posix.fdReadBuf fd (castPtr ptr) (fromIntegral chunkSize)
             BS.packCStringLen (castPtr ptr, fromIntegral n)
-        if BS.null chunk then
-            return acc
-        else
-            go (acc <> Builder.byteString chunk)
+        if BS.null chunk
+            then
+                return acc
+            else
+                go (acc <> Builder.byteString chunk)
 
 
 -- | Seek to @offset@ and read the remainder of a file descriptor into a lazy
diff --git a/src/Atelier/Effects/Process.hs b/src/Atelier/Effects/Process.hs
--- a/src/Atelier/Effects/Process.hs
+++ b/src/Atelier/Effects/Process.hs
@@ -39,7 +39,8 @@
 
       -- * Interpreters
     , runProcessIO
-    ) where
+    )
+where
 
 import Control.Exception (IOException, catch)
 import Effectful (Effect, IOE)
diff --git a/src/Atelier/Effects/Process/Internal.hs b/src/Atelier/Effects/Process/Internal.hs
--- a/src/Atelier/Effects/Process/Internal.hs
+++ b/src/Atelier/Effects/Process/Internal.hs
@@ -5,7 +5,8 @@
 -- operations may target the wrong group. Production code should not import this.
 module Atelier.Effects.Process.Internal
     ( RunningProcess (..)
-    ) where
+    )
+where
 
 import System.Process.Typed qualified as TP
 
diff --git a/src/Atelier/Effects/Publishing/Pub.hs b/src/Atelier/Effects/Publishing/Pub.hs
--- a/src/Atelier/Effects/Publishing/Pub.hs
+++ b/src/Atelier/Effects/Publishing/Pub.hs
@@ -9,7 +9,8 @@
     , map
     , mapM
     , consume
-    ) where
+    )
+where
 
 import Effectful (Effect)
 import Effectful.Dispatch.Dynamic (interpret_)
diff --git a/src/Atelier/Effects/Publishing/Sub.hs b/src/Atelier/Effects/Publishing/Sub.hs
--- a/src/Atelier/Effects/Publishing/Sub.hs
+++ b/src/Atelier/Effects/Publishing/Sub.hs
@@ -12,7 +12,8 @@
     , listenUntilM_
     , forkListener
     , forkListener_
-    ) where
+    )
+where
 
 import Data.Time (UTCTime)
 import Effectful (Effect, inject)
@@ -149,4 +150,6 @@
 
 
 data OnceEx ev = OnceEx ev
+
+
 instance Show (OnceEx ev) where show _ = "OnceEx"
diff --git a/src/Atelier/Effects/Tally.hs b/src/Atelier/Effects/Tally.hs
--- a/src/Atelier/Effects/Tally.hs
+++ b/src/Atelier/Effects/Tally.hs
@@ -33,7 +33,8 @@
       -- * Interpreters
     , runTally
     , runTallyConst
-    ) where
+    )
+where
 
 import Effectful (Effect)
 import Effectful.Concurrent (Concurrent)
diff --git a/src/Atelier/Effects/Timeout.hs b/src/Atelier/Effects/Timeout.hs
--- a/src/Atelier/Effects/Timeout.hs
+++ b/src/Atelier/Effects/Timeout.hs
@@ -16,7 +16,8 @@
     , timeout
     , timeout_
     , runTimeout
-    ) where
+    )
+where
 
 import Effectful (Effect, IOE, Limit (..), Persistence (..), UnliftStrategy (..))
 import Effectful.Dispatch.Dynamic (interpret, localUnliftIO)
diff --git a/src/Atelier/Effects/UUID.hs b/src/Atelier/Effects/UUID.hs
--- a/src/Atelier/Effects/UUID.hs
+++ b/src/Atelier/Effects/UUID.hs
@@ -9,7 +9,8 @@
     , gen
     , runGenUUID
     , runGenUUIDConst
-    ) where
+    )
+where
 
 import Data.UUID (UUID)
 import Data.UUID.V4 (nextRandom)
diff --git a/src/Atelier/Effects/Yield.hs b/src/Atelier/Effects/Yield.hs
--- a/src/Atelier/Effects/Yield.hs
+++ b/src/Atelier/Effects/Yield.hs
@@ -28,7 +28,8 @@
     , catMaybes
     , changes
     , filter
-    ) where
+    )
+where
 
 import Effectful.Dispatch.Dynamic (impose_, interpose_)
 import Effectful.State.Static.Shared (evalState, get)
diff --git a/src/Atelier/Exception.hs b/src/Atelier/Exception.hs
--- a/src/Atelier/Exception.hs
+++ b/src/Atelier/Exception.hs
@@ -11,7 +11,8 @@
     , trySyncIO
     , catchSyncIO
     , isSyncException
-    ) where
+    )
+where
 
 import Effectful.Exception (SomeAsyncException, isSyncException)
 
@@ -37,7 +38,8 @@
 catchSyncIO :: (HasCallStack) => IO a -> (SomeException -> IO a) -> IO a
 catchSyncIO f g =
     f `E.catch` \e ->
-        if isSyncException e then
-            g e
-        else
-            E.throwIO e
+        if isSyncException e
+            then
+                g e
+            else
+                E.throwIO e
diff --git a/src/Atelier/Time.hs b/src/Atelier/Time.hs
--- a/src/Atelier/Time.hs
+++ b/src/Atelier/Time.hs
@@ -1,16 +1,5 @@
-{-# OPTIONS_GHC -Wno-orphans #-}
-
 -- | Strongly-typed time units and conversions.
 --
--- Re-exports the duration types from "Data.Time.Units" — so a single import
--- covers 'Microsecond', 'Millisecond', 'Second', 'Minute' and 'Hour' — and adds
--- conversion helpers plus JSON instances for those types. Each unit serialises
--- as an integer count of itself: a 'Second' as a whole number of seconds, a
--- 'Millisecond' as milliseconds, and so on.
---
--- Note: the 'FromJSON' and 'ToJSON' instances for the time-unit types are
--- orphans, defined here because neither @aeson@ nor @time-units@ provides them.
---
 -- @
 -- nominalDiffTime 1.5 :: Millisecond        -- 1.5s rounded to 1500ms
 -- convertUnit (5 :: Minute) :: Second       -- 300
@@ -30,11 +19,24 @@
     , fromMicroseconds
     , toMicroseconds
     , convertUnit
-    ) where
 
+      -- * Utility newtypes for converting time to and from JSON
+    , AsJsonMicrosecond (..)
+    , AsRawUnit (..)
+    )
+where
+
 import Data.Aeson (FromJSON (..), ToJSON (..))
 import Data.Time (NominalDiffTime)
-import Data.Time.Units (Hour, Microsecond, Millisecond, Minute, Second, TimeUnit, convertUnit, fromMicroseconds, toMicroseconds)
+import Data.Time.Units
+    ( Hour
+    , Microsecond
+    , Millisecond
+    , Minute
+    , Second
+    , TimeUnit (..)
+    , convertUnit
+    )
 
 
 -- | Convert a 'NominalDiffTime' to any 'TimeUnit', rounding to the nearest
@@ -43,44 +45,25 @@
 nominalDiffTime = fromMicroseconds . round @Double . (* 1_000_000) . realToFrac
 
 
--- Orphan instances for JSON deserialization of time unit types.
--- All time units in Data.Time.Units wrap Integer, so we parse as Integer.
-
-instance FromJSON Microsecond where
-    parseJSON = fmap fromInteger . parseJSON
-
-
-instance FromJSON Millisecond where
-    parseJSON = fmap fromInteger . parseJSON
-
-
-instance FromJSON Second where
-    parseJSON = fmap fromInteger . parseJSON
-
-
-instance FromJSON Minute where
-    parseJSON = fmap fromInteger . parseJSON
-
-
-instance FromJSON Hour where
-    parseJSON = fmap fromInteger . parseJSON
+newtype AsJsonMicrosecond unit = AsJsonMicrosecond {getAsJsonMicroseconds :: unit}
+    deriving stock (Eq, Generic, Show)
 
 
-instance ToJSON Microsecond where
-    toJSON us = toJSON (toMicroseconds us)
+instance (TimeUnit unit) => ToJSON (AsJsonMicrosecond unit) where
+    toJSON = toJSON . toMicroseconds . getAsJsonMicroseconds
 
 
-instance ToJSON Millisecond where
-    toJSON ms = toJSON (toMicroseconds ms `div` 1000)
+instance (TimeUnit unit) => FromJSON (AsJsonMicrosecond unit) where
+    parseJSON = fmap (AsJsonMicrosecond . fromMicroseconds) . parseJSON
 
 
-instance ToJSON Second where
-    toJSON s = toJSON (toMicroseconds s `div` 1_000_000)
+newtype AsRawUnit unit = AsRawUnit {getAsRawUnit :: unit}
+    deriving stock (Eq, Generic, Show)
 
 
-instance ToJSON Minute where
-    toJSON m = toJSON (toMicroseconds m `div` 60_000_000)
+instance (Integral unit) => ToJSON (AsRawUnit unit) where
+    toJSON = toJSON . toInteger . getAsRawUnit
 
 
-instance ToJSON Hour where
-    toJSON h = toJSON (toMicroseconds h `div` 3_600_000_000)
+instance (Integral unit) => FromJSON (AsRawUnit unit) where
+    parseJSON = fmap (AsRawUnit . fromInteger) . parseJSON
diff --git a/src/Atelier/Types/Base64.hs b/src/Atelier/Types/Base64.hs
--- a/src/Atelier/Types/Base64.hs
+++ b/src/Atelier/Types/Base64.hs
@@ -10,7 +10,8 @@
 -- @
 module Atelier.Types.Base64
     ( Base64 (..)
-    ) where
+    )
+where
 
 import Data.Aeson (FromJSON (..), ToJSON (..), withText)
 
diff --git a/src/Atelier/Types/QuietSnake.hs b/src/Atelier/Types/QuietSnake.hs
--- a/src/Atelier/Types/QuietSnake.hs
+++ b/src/Atelier/Types/QuietSnake.hs
@@ -15,9 +15,19 @@
 -- @
 module Atelier.Types.QuietSnake
     ( QuietSnake (..)
-    ) where
+    )
+where
 
-import Data.Aeson (FromJSON (..), GFromJSON, GToJSON, Options, ToJSON (..), Zero, genericParseJSON, genericToJSON)
+import Data.Aeson
+    ( FromJSON (..)
+    , GFromJSON
+    , GToJSON
+    , Options
+    , ToJSON (..)
+    , Zero
+    , genericParseJSON
+    , genericToJSON
+    )
 import Data.Aeson.Types (defaultOptions, fieldLabelModifier)
 import Data.Default (Default (..))
 import GHC.Generics (Rep)
diff --git a/src/Atelier/Types/Semaphore.hs b/src/Atelier/Types/Semaphore.hs
--- a/src/Atelier/Types/Semaphore.hs
+++ b/src/Atelier/Types/Semaphore.hs
@@ -10,7 +10,8 @@
     , set
     , peek
     , withSemaphore
-    ) where
+    )
+where
 
 import Effectful.Concurrent.STM (Concurrent, atomically)
 
diff --git a/src/Atelier/Types/Semaphore/STM.hs b/src/Atelier/Types/Semaphore/STM.hs
--- a/src/Atelier/Types/Semaphore/STM.hs
+++ b/src/Atelier/Types/Semaphore/STM.hs
@@ -9,7 +9,8 @@
     , set
     , peek
     , withSemaphore
-    ) where
+    )
+where
 
 import Effectful.Concurrent.STM
     ( Concurrent
diff --git a/src/Atelier/Types/WithDefaults.hs b/src/Atelier/Types/WithDefaults.hs
--- a/src/Atelier/Types/WithDefaults.hs
+++ b/src/Atelier/Types/WithDefaults.hs
@@ -17,7 +17,8 @@
 -- Parsing @{\"verbose\": true}@ then yields @Config { retries = 3, verbose = True }@.
 module Atelier.Types.WithDefaults
     ( WithDefaults (..)
-    ) where
+    )
+where
 
 import Data.Aeson (FromJSON (..), ToJSON (..), Value (..))
 import Data.Default (Default (..))
diff --git a/test/Unit/Atelier/Effects/AwaitSpec.hs b/test/Unit/Atelier/Effects/AwaitSpec.hs
--- a/test/Unit/Atelier/Effects/AwaitSpec.hs
+++ b/test/Unit/Atelier/Effects/AwaitSpec.hs
@@ -6,6 +6,7 @@
 import Effectful.Writer.Static.Shared (runWriter, tell)
 import Test.Hspec (Spec, describe, it, shouldBe)
 
+import Data.List qualified as List
 import Effectful.Concurrent.STM qualified as STM
 
 import Atelier.Effects.Chan (runChan)
@@ -70,7 +71,7 @@
         (_, result) <-
             runTest
                 $ Await.awaitYield @Int (Yield.yield 99 >> blockForever)
-                $ fmap (* 2) Await.await >>= tell . one
+                $ fmap (* 2) Await.await >>= tell . List.singleton
         result `shouldBe` [198]
 
     describe "when the awaiter finishes before the yielder"
diff --git a/test/Unit/Atelier/Effects/Cache/SingleflightSpec.hs b/test/Unit/Atelier/Effects/Cache/SingleflightSpec.hs
--- a/test/Unit/Atelier/Effects/Cache/SingleflightSpec.hs
+++ b/test/Unit/Atelier/Effects/Cache/SingleflightSpec.hs
@@ -197,7 +197,8 @@
             it "propagates exception to all concurrent waiters" $ do
                 let action = runSingleflightTest $ do
                         sem <- Sem.new
-                        a1 <- Conc.fork $ withCache @Int @Int 1 (slowCompute sem 42 >> throwIO (TestException "concurrent-boom"))
+                        a1 <-
+                            Conc.fork $ withCache @Int @Int 1 (slowCompute sem 42 >> throwIO (TestException "concurrent-boom"))
                         Delay.wait (1 :: Millisecond)
                         a2 <- Conc.fork $ withCache @Int @Int 1 (compute 99)
                         Sem.signal sem
diff --git a/test/Unit/Atelier/Effects/CacheSpec.hs b/test/Unit/Atelier/Effects/CacheSpec.hs
--- a/test/Unit/Atelier/Effects/CacheSpec.hs
+++ b/test/Unit/Atelier/Effects/CacheSpec.hs
@@ -14,7 +14,14 @@
 import Hedgehog.Gen qualified as Gen
 import Hedgehog.Range qualified as Range
 
-import Atelier.Effects.Cache (Config (..), cacheDelete, cacheInsert, cacheLookup, cacheModify, runCacheTtlWithWait)
+import Atelier.Effects.Cache
+    ( Config (..)
+    , cacheDelete
+    , cacheInsert
+    , cacheLookup
+    , cacheModify
+    , runCacheTtlWithWait
+    )
 import Atelier.Effects.Clock (runClockState)
 import Atelier.Effects.Conc (runConc)
 import Atelier.Effects.Delay (runDelay)
diff --git a/test/Unit/Atelier/Effects/Conc/TeardownStressSpec.hs b/test/Unit/Atelier/Effects/Conc/TeardownStressSpec.hs
--- a/test/Unit/Atelier/Effects/Conc/TeardownStressSpec.hs
+++ b/test/Unit/Atelier/Effects/Conc/TeardownStressSpec.hs
@@ -37,11 +37,15 @@
 
 spec_ConcTeardownStress :: Spec
 spec_ConcTeardownStress = do
-    iterations <- runIO (fromMaybe defaultIterations . (>>= readMaybe) <$> lookupEnv "ATELIER_CONC_STRESS_N")
-    timeoutSecs <- runIO (fromMaybe defaultTimeoutSecs . (>>= readMaybe) <$> lookupEnv "ATELIER_CONC_STRESS_TIMEOUT_S")
+    iterations <-
+        runIO (fromMaybe defaultIterations . (>>= readMaybe) <$> lookupEnv "ATELIER_CONC_STRESS_N")
+    timeoutSecs <-
+        runIO (fromMaybe defaultTimeoutSecs . (>>= readMaybe) <$> lookupEnv "ATELIER_CONC_STRESS_TIMEOUT_S")
     runSpin <- runIO (isJust <$> lookupEnv "ATELIER_CONC_SPIN")
 
-    publishDelayUs <- runIO (fromMaybe defaultPublishDelayUs . (>>= readMaybe) <$> lookupEnv "ATELIER_CONC_STRESS_DELAY_US")
+    publishDelayUs <-
+        runIO
+            (fromMaybe defaultPublishDelayUs . (>>= readMaybe) <$> lookupEnv "ATELIER_CONC_STRESS_DELAY_US")
 
     let testTimeoutMicros = timeoutSecs * 1_000_000
 
diff --git a/test/Unit/Atelier/Effects/FileWatcherSpec.hs b/test/Unit/Atelier/Effects/FileWatcherSpec.hs
--- a/test/Unit/Atelier/Effects/FileWatcherSpec.hs
+++ b/test/Unit/Atelier/Effects/FileWatcherSpec.hs
@@ -12,7 +12,17 @@
 import Hedgehog.Gen qualified as Gen
 import Hedgehog.Range qualified as Range
 
-import Atelier.Effects.FileWatcher (FileEvent (..), FileWatcher, Watch, deduplicateDirs, dir, dirWhere, matchesAny, runFileWatcherScripted, watchFilePaths)
+import Atelier.Effects.FileWatcher
+    ( FileEvent (..)
+    , FileWatcher
+    , Watch
+    , deduplicateDirs
+    , dir
+    , dirWhere
+    , matchesAny
+    , runFileWatcherScripted
+    , watchFilePaths
+    )
 
 
 spec_FileWatcher :: Spec
