packages feed

atelier-core 0.3.0.0 → 0.4.0.0

raw patch · 18 files changed

+85/−1107 lines, 18 filesdep −asyncdep −contra-tracerdep −hs-opentelemetry-sdkdep ~aesondep ~atelier-coredep ~atelier-preludePVP ok

version bump matches the API change (PVP)

Dependencies removed: async, contra-tracer, hs-opentelemetry-sdk

Dependency ranges changed: aeson, atelier-core, atelier-prelude, http-api-data, time

API changes (from Hackage documentation)

- Atelier.Component: Component :: ~Text -> Eff es () -> Eff es [Listener es] -> Eff es [Trigger es] -> Eff es () -> Component (es :: [Effect])
- Atelier.Component: [listeners] :: Component (es :: [Effect]) -> Eff es [Listener es]
- Atelier.Component: [name] :: Component (es :: [Effect]) -> ~Text
- Atelier.Component: [setup] :: Component (es :: [Effect]) -> Eff es ()
- Atelier.Component: [start] :: Component (es :: [Effect]) -> Eff es ()
- Atelier.Component: [triggers] :: Component (es :: [Effect]) -> Eff es [Trigger es]
- Atelier.Component: data Component (es :: [Effect])
- Atelier.Component: defaultComponent :: forall (es :: [Effect]). HasCallStack => Component es
- Atelier.Component: runComponent :: forall (es :: [Effect]). (Conc :> es, Tracing :> es) => Component es -> Eff es ()
- Atelier.Component: runSystem :: forall (es :: [Effect]). (Conc :> es, Log :> es, Tracing :> es) => [Component es] -> Eff es ()
- Atelier.Component: type Listener (es :: [Effect]) = Eff es Void
- Atelier.Component: type Trigger (es :: [Effect]) = Eff es Void
- Atelier.Effects.Conc.Traced: runConc :: forall (es :: [Effect]) a. (Concurrent :> es, IOE :> es, Tracing :> es) => Eff (Conc ': es) a -> Eff es a
- Atelier.Effects.Conc.Traced: runConcByConfig :: forall (es :: [Effect]) a. (Concurrent :> es, IOE :> es, Reader TracingConfig :> es, Tracing :> es) => Eff (Conc ': es) a -> Eff es a
- Atelier.Effects.Conc.Traced: runConcTraced :: forall (es :: [Effect]) a. (Concurrent :> es, IOE :> es, Tracing :> es) => Scope -> Eff (Conc ': es) a -> Eff es a
- Atelier.Effects.Conc.Traced: withConcTracingLinks :: forall (es :: [Effect]) a. (Conc :> es, Tracing :> es) => Eff es a -> Eff es a
- Atelier.Effects.Monitoring.Metrics: counterAdd :: forall (es :: [Effect]). (HasCallStack, Metrics :> es) => Text -> Double -> Eff es ()
- Atelier.Effects.Monitoring.Metrics: counterInc :: forall (es :: [Effect]). (HasCallStack, Metrics :> es) => Text -> Eff es ()
- Atelier.Effects.Monitoring.Metrics: data Metrics (a :: Type -> Type) b
- Atelier.Effects.Monitoring.Metrics: exportMetrics :: forall (es :: [Effect]). (HasCallStack, Metrics :> es) => Eff es Text
- Atelier.Effects.Monitoring.Metrics: gaugeDec :: forall (es :: [Effect]). (HasCallStack, Metrics :> es) => Text -> Eff es ()
- Atelier.Effects.Monitoring.Metrics: gaugeInc :: forall (es :: [Effect]). (HasCallStack, Metrics :> es) => Text -> Eff es ()
- Atelier.Effects.Monitoring.Metrics: gaugeSet :: forall (es :: [Effect]). (HasCallStack, Metrics :> es) => Text -> Double -> Eff es ()
- Atelier.Effects.Monitoring.Metrics: histogramObserve :: forall (es :: [Effect]). (HasCallStack, Metrics :> es) => Text -> Double -> Eff es ()
- Atelier.Effects.Monitoring.Metrics: runMetrics :: forall (es :: [Effect]) a. (Clock :> es, IOE :> es, Tracing :> es) => Eff (Metrics ': es) a -> Eff es a
- Atelier.Effects.Monitoring.Metrics: runMetricsNoOp :: forall (es :: [(Type -> Type) -> Type -> Type]) a. Eff (Metrics ': es) a -> Eff es a
- Atelier.Effects.Monitoring.Metrics: withHistogramTiming :: forall a (es :: [Effect]). (HasCallStack, Metrics :> es) => Text -> Eff es a -> Eff es a
- Atelier.Effects.Monitoring.Metrics.Registry: addCounter :: MetricHandles -> Text -> Double -> IO ()
- Atelier.Effects.Monitoring.Metrics.Registry: data MetricHandles
- Atelier.Effects.Monitoring.Metrics.Registry: decGauge :: MetricHandles -> Text -> IO ()
- Atelier.Effects.Monitoring.Metrics.Registry: incCounter :: MetricHandles -> Text -> IO ()
- Atelier.Effects.Monitoring.Metrics.Registry: incGauge :: MetricHandles -> Text -> IO ()
- Atelier.Effects.Monitoring.Metrics.Registry: initMetricHandles :: IO MetricHandles
- Atelier.Effects.Monitoring.Metrics.Registry: observeHistogram :: MetricHandles -> Text -> Double -> IO ()
- Atelier.Effects.Monitoring.Metrics.Registry: setGauge :: MetricHandles -> Text -> Double -> IO ()
- Atelier.Effects.Monitoring.Metrics.Server: data MetricsServer (a :: Type -> Type) b
- Atelier.Effects.Monitoring.Metrics.Server: runMetricsServer :: forall (es :: [Effect]). (HasCallStack, MetricsServer :> es) => Int -> Eff es ()
- Atelier.Effects.Monitoring.Metrics.Server: runMetricsServerIO :: forall (es :: [Effect]) a. IOE :> es => Eff (MetricsServer ': es) a -> Eff es a
- Atelier.Effects.Monitoring.Tracing: Error :: Text -> SpanStatus
- Atelier.Effects.Monitoring.Tracing: Ok :: SpanStatus
- Atelier.Effects.Monitoring.Tracing: ToAttributeShow :: a -> ToAttributeShow a
- Atelier.Effects.Monitoring.Tracing: TracingConfig :: Bool -> Text -> Text -> TracingConfig
- Atelier.Effects.Monitoring.Tracing: [AddAttribute] :: forall attr (a :: Type -> Type). ToAttribute attr => Text -> attr -> Tracing a ()
- Atelier.Effects.Monitoring.Tracing: [AddEvent] :: forall attr (a :: Type -> Type). ToAttribute attr => Text -> [(Text, attr)] -> Tracing a ()
- Atelier.Effects.Monitoring.Tracing: [Attr] :: forall a. ToAttribute a => a -> Attr
- Atelier.Effects.Monitoring.Tracing: [GetCurrentContext] :: forall (a :: Type -> Type). Tracing a Context
- Atelier.Effects.Monitoring.Tracing: [GetSpanContext] :: forall (a :: Type -> Type). Tracing a (Maybe SpanContext)
- Atelier.Effects.Monitoring.Tracing: [SetStatus] :: forall (a :: Type -> Type). SpanStatus -> Tracing a ()
- Atelier.Effects.Monitoring.Tracing: [WithSpanLinked] :: forall (a :: Type -> Type) b. Text -> [SpanContext] -> a b -> Tracing a b
- Atelier.Effects.Monitoring.Tracing: [WithSpan] :: forall (a :: Type -> Type) b. Text -> a b -> Tracing a b
- Atelier.Effects.Monitoring.Tracing: [enabled] :: TracingConfig -> Bool
- Atelier.Effects.Monitoring.Tracing: [getToAttributeShow] :: ToAttributeShow a -> a
- Atelier.Effects.Monitoring.Tracing: [otlpEndpoint] :: TracingConfig -> Text
- Atelier.Effects.Monitoring.Tracing: [serviceName] :: TracingConfig -> Text
- Atelier.Effects.Monitoring.Tracing: addAttribute :: forall attr (es :: [Effect]). (HasCallStack, Tracing :> es, ToAttribute attr) => Text -> attr -> Eff es ()
- Atelier.Effects.Monitoring.Tracing: addEvent :: forall attr (es :: [Effect]). (HasCallStack, Tracing :> es, ToAttribute attr) => Text -> [(Text, attr)] -> Eff es ()
- Atelier.Effects.Monitoring.Tracing: class ToAttribute a
- Atelier.Effects.Monitoring.Tracing: data Attr
- Atelier.Effects.Monitoring.Tracing: data SpanStatus
- Atelier.Effects.Monitoring.Tracing: data Tracing (a :: Type -> Type) b
- Atelier.Effects.Monitoring.Tracing: data TracingConfig
- Atelier.Effects.Monitoring.Tracing: getSpanContext :: forall (es :: [Effect]). (HasCallStack, Tracing :> es) => Eff es (Maybe SpanContext)
- Atelier.Effects.Monitoring.Tracing: instance Data.Aeson.Types.FromJSON.FromJSON Atelier.Effects.Monitoring.Tracing.TracingConfig
- Atelier.Effects.Monitoring.Tracing: instance Data.Aeson.Types.ToJSON.ToJSON Atelier.Effects.Monitoring.Tracing.TracingConfig
- Atelier.Effects.Monitoring.Tracing: instance Data.Default.Internal.Default Atelier.Effects.Monitoring.Tracing.TracingConfig
- Atelier.Effects.Monitoring.Tracing: instance GHC.Classes.Eq Atelier.Effects.Monitoring.Tracing.SpanStatus
- Atelier.Effects.Monitoring.Tracing: instance GHC.Classes.Eq Atelier.Effects.Monitoring.Tracing.TracingConfig
- Atelier.Effects.Monitoring.Tracing: instance GHC.Internal.Generics.Generic Atelier.Effects.Monitoring.Tracing.TracingConfig
- Atelier.Effects.Monitoring.Tracing: instance GHC.Internal.Show.Show Atelier.Effects.Monitoring.Tracing.SpanStatus
- Atelier.Effects.Monitoring.Tracing: instance GHC.Internal.Show.Show Atelier.Effects.Monitoring.Tracing.TracingConfig
- Atelier.Effects.Monitoring.Tracing: instance GHC.Internal.Show.Show a => OpenTelemetry.Attributes.Attribute.ToAttribute (Atelier.Effects.Monitoring.Tracing.ToAttributeShow a)
- Atelier.Effects.Monitoring.Tracing: instance GHC.Internal.Show.Show a => OpenTelemetry.Attributes.Attribute.ToPrimitiveAttribute (Atelier.Effects.Monitoring.Tracing.ToAttributeShow a)
- Atelier.Effects.Monitoring.Tracing: instance OpenTelemetry.Attributes.Attribute.ToAttribute Atelier.Effects.Monitoring.Tracing.Attr
- Atelier.Effects.Monitoring.Tracing: newtype ToAttributeShow a
- Atelier.Effects.Monitoring.Tracing: runTracing :: forall (es :: [Effect]) a. (IOE :> es, Timeout :> es) => Bool -> Text -> Text -> Eff (Tracing ': es) a -> Eff es a
- Atelier.Effects.Monitoring.Tracing: runTracingFromConfig :: forall (es :: [Effect]) a. (IOE :> es, Reader TracingConfig :> es, Timeout :> es) => Eff (Tracing ': es) a -> Eff es a
- Atelier.Effects.Monitoring.Tracing: runTracingNoOp :: forall (es :: [(Type -> Type) -> Type -> Type]) a. Eff (Tracing ': es) a -> Eff es a
- Atelier.Effects.Monitoring.Tracing: setStatus :: forall (es :: [Effect]). (HasCallStack, Tracing :> es) => SpanStatus -> Eff es ()
- Atelier.Effects.Monitoring.Tracing: toAttribute :: ToAttribute a => a -> Attribute
- Atelier.Effects.Monitoring.Tracing: type SpanContext = SpanContext
- Atelier.Effects.Monitoring.Tracing: withLinkPropagation :: forall (es :: [Effect]) a. Tracing :> es => Maybe SpanContext -> Eff es a -> Eff es a
- Atelier.Effects.Monitoring.Tracing: withSpan :: forall a (es :: [Effect]). (HasCallStack, Tracing :> es) => Text -> Eff es a -> Eff es a
- Atelier.Effects.Monitoring.Tracing: withSpanLinked :: forall a (es :: [Effect]). (HasCallStack, Tracing :> es) => Text -> [SpanContext] -> Eff es a -> Eff es a
- Atelier.Effects.Monitoring.Tracing.Provider: TracingState :: TracerProvider -> Tracer -> TracingState
- Atelier.Effects.Monitoring.Tracing.Provider: [tracerProvider] :: TracingState -> TracerProvider
- Atelier.Effects.Monitoring.Tracing.Provider: [tracer] :: TracingState -> Tracer
- Atelier.Effects.Monitoring.Tracing.Provider: data TracingState
- Atelier.Effects.Monitoring.Tracing.Provider: initTracingState :: Text -> Text -> IO TracingState
- Atelier.Effects.Monitoring.Tracing.Provider: shutdownTracingState :: TracingState -> IO ()
- Atelier.Effects.Publishing: runPubSub_ :: forall event (es :: [Effect]) a. (Chan :> es, Clock :> es) => Eff (Pub event ': (Sub event ': es)) a -> Eff es a
+ Atelier.Effects.Cache.Singleflight: [RemoveFromCache] :: forall key value (a :: Type -> Type). [key] -> Singleflight key value a ()
+ Atelier.Effects.Cache.Singleflight: [UpdateCache] :: forall key value (a :: Type -> Type). [(key, value)] -> Singleflight key value a ()
+ Atelier.Effects.Cache.Singleflight: [WithCache] :: forall key (a :: Type -> Type) value. Hashable key => key -> a value -> Singleflight key value a value
+ Atelier.Effects.Env: lookupEnv :: forall (es :: [Effect]). (HasCallStack, Env :> es) => String -> Eff es (Maybe String)
+ Atelier.Effects.FileSystem: [DoesDirectoryExist] :: forall (a :: Type -> Type). FilePath -> FileSystem a Bool
+ Atelier.Effects.FileSystem: [WriteFileBS] :: forall (a :: Type -> Type). FilePath -> ByteString -> FileSystem a ()
+ Atelier.Effects.FileSystem: [WriteFileLBS] :: forall (a :: Type -> Type). FilePath -> LByteString -> FileSystem a ()
+ Atelier.Effects.FileSystem: doesDirectoryExist :: forall (es :: [Effect]). (HasCallStack, FileSystem :> es) => FilePath -> Eff es Bool
+ Atelier.Effects.FileSystem: writeFileBS :: forall (es :: [Effect]). (HasCallStack, FileSystem :> es) => FilePath -> ByteString -> Eff es ()
+ Atelier.Effects.FileSystem: writeFileLBS :: forall (es :: [Effect]). (HasCallStack, FileSystem :> es) => FilePath -> LByteString -> Eff es ()
- Atelier.Effects.Cache.Singleflight: runSingleflight :: forall key value (es :: [Effect]) a. (Concurrent :> es, Hashable key, Tracing :> es) => Eff (Singleflight key value ': es) a -> Eff es a
+ Atelier.Effects.Cache.Singleflight: runSingleflight :: forall key value (es :: [Effect]) a. (Concurrent :> es, Hashable key) => Eff (Singleflight key value ': es) a -> Eff es a
- Atelier.Effects.FileSystem: readFileLbs :: forall (es :: [Effect]). FileSystem :> es => FilePath -> Eff es ByteString
+ Atelier.Effects.FileSystem: readFileLbs :: forall (es :: [Effect]). FileSystem :> es => FilePath -> Eff es LByteString
- Atelier.Effects.FileSystem: readFileLbsFrom :: forall (es :: [Effect]). (HasCallStack, FileSystem :> es) => FilePath -> FileOffset -> Eff es ByteString
+ Atelier.Effects.FileSystem: readFileLbsFrom :: forall (es :: [Effect]). (HasCallStack, FileSystem :> es) => FilePath -> FileOffset -> Eff es LByteString
- Atelier.Effects.Publishing: runPubSub :: forall event (es :: [Effect]) a. (Chan :> es, Clock :> es, Tracing :> es) => Eff (Pub event ': (Sub event ': es)) a -> Eff es a
+ Atelier.Effects.Publishing: runPubSub :: forall event (es :: [Effect]) a. (Chan :> es, Clock :> es) => Eff (Pub event ': (Sub event ': es)) a -> Eff es a

Files

CHANGELOG.md view
@@ -7,6 +7,34 @@  ## [Unreleased] +## [0.4.0.0] - 2026-08-14++### Added++- `Atelier.Effects.Env`: `lookupEnv` picks out a single environment variable.++### Changed++- Major version bump for the following dependencies' upper version bounds:+  - `aeson`+  - `http-api-data`+  - `time`++### Removed++- Move all metrics and observability tooling to `atelier-monitoring`:+  - `Atelier.Component`: moved to `atelier-monitoring`.+  - `Atelier.Effects.Conc.Traced`: moved to `atelier-monitoring`.+  - `Atelier.Effects.Monitoring.Metrics`: moved to `atelier-monitoring`.+  - `Atelier.Effects.Monitoring.Metrics.Server`: moved to `atelier-monitoring`.+  - `Atelier.Effects.Monitoring.Metrics.Registry`: moved to `atelier-monitoring`.+  - `Atelier.Effects.Cache.Singleflight`: removed tracing capability. Old version+    with tracing capability is available in `atelier-monitoring` as+    `Atelier.Effects.Cache.Singleflight.Traced`.+  - `Atelier.Effects.Publishing#runPubSub`: moved to `atelier-monitoring` in+    `Atelier.Effects.Publishing.Traced`.+  - `Atelier.Effects.Publshing#runPubSub_`: renamed to `runPubSub`.+ ## [0.3.0.0] - 2026-08-06  ### Added
atelier-core.cabal view
@@ -5,14 +5,14 @@ -- see: https://github.com/sol/hpack  name:           atelier-core-version:        0.3.0.0+version:        0.4.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 homepage:       https://github.com/tweag/tricorder#readme bug-reports:    https://github.com/tweag/tricorder/issues-author:         Christian Georgii-maintainer:     christian.georgii@tweag.io+author:         Victor Nascimento Bakke+maintainer:     victor.bakke@tweag.io license:        MIT license-file:   LICENSE build-type:     Simple@@ -31,7 +31,6 @@  library   exposed-modules:-      Atelier.Component       Atelier.Config       Atelier.Effects.Arguments       Atelier.Effects.Await@@ -41,7 +40,6 @@       Atelier.Effects.Chan       Atelier.Effects.Clock       Atelier.Effects.Conc-      Atelier.Effects.Conc.Traced       Atelier.Effects.Console       Atelier.Effects.Debounce       Atelier.Effects.Delay@@ -54,11 +52,6 @@       Atelier.Effects.Internal.Coroutine       Atelier.Effects.Iterator       Atelier.Effects.Log-      Atelier.Effects.Monitoring.Metrics-      Atelier.Effects.Monitoring.Metrics.Registry-      Atelier.Effects.Monitoring.Metrics.Server-      Atelier.Effects.Monitoring.Tracing-      Atelier.Effects.Monitoring.Tracing.Provider       Atelier.Effects.Posix.Daemons       Atelier.Effects.Posix.IO       Atelier.Effects.Process@@ -104,14 +97,13 @@       TypeFamilies   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.*-    , atelier-prelude ==0.1.*+      aeson >=2.2 && <2.4+    , atelier-prelude >=0.1 && <0.3     , base >=4.18 && <4.23     , base64-bytestring ==1.2.*     , bytestring >=0.11 && <0.13     , casing ==0.1.*     , containers >=0.6 && <0.9-    , contra-tracer ==0.2.*     , daemons ==0.4.*     , data-default ==0.8.*     , directory ==1.3.*@@ -121,9 +113,7 @@     , effectful-th ==1.0.*     , filepath >=1.4 && <1.6     , fsnotify ==0.4.*-    , hs-opentelemetry-api ==0.3.*-    , hs-opentelemetry-sdk ==0.1.*-    , http-api-data ==0.7.*+    , http-api-data >=0.5.1 && <0.8     , http-types ==0.12.*     , ki ==1.0.*     , list-t ==1.0.*@@ -134,7 +124,7 @@     , stm ==2.5.*     , stm-containers ==1.2.*     , text ==2.1.*-    , time >=1.12 && <1.16+    , time >=1.12 && <1.17     , time-units ==1.0.*     , typed-process ==0.2.*     , unagi-chan ==0.4.*@@ -159,7 +149,6 @@       Unit.Atelier.Effects.CacheSpec       Unit.Atelier.Effects.ChanSpec       Unit.Atelier.Effects.Conc.TeardownStressSpec-      Unit.Atelier.Effects.Conc.TracedSpec       Unit.Atelier.Effects.ConcSpec       Unit.Atelier.Effects.ConsoleSpec       Unit.Atelier.Effects.DebounceSpec@@ -200,10 +189,9 @@   build-tool-depends:       tasty-discover:tasty-discover   build-depends:-      aeson ==2.2.*-    , async ==2.2.*-    , atelier-core ==0.3.*-    , atelier-prelude ==0.1.*+      aeson >=2.2 && <2.4+    , atelier-core >=0.3 && <0.5+    , atelier-prelude >=0.1 && <0.3     , base >=4.18 && <4.23     , bytestring >=0.11 && <0.13     , containers >=0.6 && <0.9@@ -219,7 +207,7 @@     , stm-containers ==1.2.*     , tasty ==1.5.*     , tasty-hspec ==1.2.*-    , time >=1.12 && <1.16+    , time >=1.12 && <1.17   mixins:       base hiding (Prelude)   default-language: GHC2021
− src/Atelier/Component.hs
@@ -1,132 +0,0 @@--- | A small component model for assembling long-running applications.------ A 'Component' bundles a named unit of work with a lifecycle: one-off 'setup',--- a set of 'listeners' and 'triggers' that run as forked threads, and a--- post-'start' action. 'runSystem' drives a collection of components through--- these phases in lockstep, so that (for example) every component has finished--- 'setup' before any 'start' action runs and publishes events the others react--- to.-module Atelier.Component-    ( -- * Components-      Component (..)-    , defaultComponent-    , Listener-    , Trigger--      -- * Running-    , runComponent-    , runSystem-    ) where--import Text.Casing (fromHumps, toQuietSnake)--import Atelier.Effects.Conc (Conc)-import Atelier.Effects.Log (Log)-import Atelier.Effects.Monitoring.Tracing (Tracing, withSpan)--import Atelier.Effects.Conc qualified as Conc-import Atelier.Effects.Log qualified as Log----- | A listener reacts to events and runs forever; it never returns normally,--- hence the 'Void' result.-type Listener es = Eff es Void----- | A trigger initiates periodic or scheduled work and, like a 'Listener', runs--- forever and never returns normally.-type Trigger es = Eff es Void----- | A named unit of application work together with its lifecycle hooks.------ Build one by overriding the fields of 'defaultComponent' you care about.-data Component es = Component-    { name :: ~Text-    -- ^ Component name for tracing-    , setup :: Eff es ()-    -- ^ Setup component (runs before listeners/triggers start)-    , listeners :: Eff es [Listener es]-    -- ^ Event listeners (react to events)-    , triggers :: Eff es [Trigger es]-    -- ^ Triggers (initiate periodic/scheduled work)-    , start :: Eff es ()-    -- ^ Post-start actions (runs after all components have started)-    }----- | A 'Component' with no-op lifecycle hooks and no listeners or triggers.------ Override the fields you need. 'name' is deliberately left as an 'error' so a--- component created without a name fails fast rather than tracing anonymously.-defaultComponent :: (HasCallStack) => Component es-defaultComponent =-    Component-        { name = error "Missing component name"-        , setup = pure ()-        , listeners = pure []-        , triggers = pure []-        , start = pure ()-        }----- | Run a component by forking its listeners and triggers-runComponent :: (Conc :> es, Tracing :> es) => Component es -> Eff es ()-runComponent c = withSpan c.name $ do-    ls <- c.listeners-    ts <- c.triggers-    traverse_ Conc.fork_ ls-    traverse_ Conc.fork_ ts----- | Run multiple components with structured lifecycle coordination------ Executes components in three sequential phases:---   1. Run @setup for all components (initialization before activation)---   2. Fork all listeners and triggers (components become active)---   3. Fork @start for all components (background startup actions)------ This separation allows components to prepare resources during setup, then perform--- work during start that depends on listeners already running (e.g., publishing events).--- The start phase is forked to allow parallel execution across components.-runSystem-    :: forall es-     . (Conc :> es, Log :> es, Tracing :> es)-    => [Component es]-    -> Eff es ()-runSystem components = Conc.scoped do-    -- Phase 1: Setup all components-    traverse_ setupComponent components--    -- Phase 2: Start all components (fork listeners/triggers)-    traverse_ startComponent components--    -- Phase 3: Fork post-start actions-    traverse_ postStartComponent components--    Conc.awaitAll-  where-    setupComponent :: Component es -> Eff es ()-    setupComponent c = do-        let name = formatName c.name-        Log.debug $ "Setting up component: " <> name-        withSpan (name <> ":setup")-            $ c.setup--    startComponent :: Component es -> Eff es ()-    startComponent c = do-        let name = formatName c.name-        Log.debug $ "Starting listeners/triggers for component: " <> name-        runComponent c--    postStartComponent :: Component es -> Eff es ()-    postStartComponent c = do-        let name = formatName c.name-        Log.debug $ "Forking start phase for component: " <> name-        void . Conc.fork-            $ withSpan (name <> ":start")-            $ c.start---formatName :: Text -> Text-formatName = toText . toQuietSnake . fromHumps . toString
src/Atelier/Effects/Cache/Singleflight.hs view
@@ -4,10 +4,9 @@ -- When several threads request the same key at once, the first runs the -- computation while the rest wait and share its result — so an expensive lookup -- happens once per key per in-flight window. Results (and exceptions) can also--- be seeded with 'updateCache' or invalidated with 'removeFromCache'. Each--- operation is traced (see "Atelier.Effects.Monitoring.Tracing").+-- be seeded with 'updateCache' or invalidated with 'removeFromCache'. module Atelier.Effects.Cache.Singleflight-    ( Singleflight+    ( Singleflight (..)     , withCache     , updateCache     , removeFromCache@@ -26,9 +25,7 @@ import Effectful.Concurrent.STM qualified as STM import StmContainers.Map qualified as Map -import Atelier.Effects.Monitoring.Tracing (Tracing, addAttribute, withSpan) - -- | Singleflight cache effect for deduplicating concurrent computations -- -- When multiple concurrent operations request the same key:@@ -55,7 +52,7 @@ -- | Run the Singleflight effect with an in-memory cache runSingleflight     :: forall key value es a-     . (Concurrent :> es, Hashable key, Tracing :> es)+     . (Concurrent :> es, Hashable key)     => Eff (Singleflight key value : es) a     -> Eff es a runSingleflight action = do@@ -64,7 +61,7 @@      -- Run with the cache in Reader context, interpreting Singleflight operations     interpretWith action $ \env -> \case-        WithCache key computation -> localSeqUnlift env $ \unlift -> withSpan "singleflight.with_cache" do+        WithCache key computation -> localSeqUnlift env $ \unlift -> do             -- Singleflight pattern: check if computation is already in-flight.             -- Also attempt a non-blocking read of any existing result in the same transaction.             (mvar, isFirst, mResult) <- STM.atomically $ do@@ -81,13 +78,10 @@              case (isFirst, mResult) of                 (_, Just (Right value)) -> do-                    addAttribute @Text "singleflight.outcome" "hit"                     pure value                 (_, Just (Left exception)) -> do-                    addAttribute @Text "singleflight.outcome" "hit"                     throwIO exception                 (True, Nothing) -> do-                    addAttribute @Text "singleflight.outcome" "compute"                     result <- unlift $ trySync computation                      -- Try to fill the TMVar with result (success or failure) for waiters@@ -113,15 +107,13 @@                             Left exception -> throwIO exception                             Right value -> pure value                 (False, Nothing) -> do-                    addAttribute @Text "singleflight.outcome" "wait"                     result <--                        withSpan "singleflight.wait"-                            $ STM.atomically+                        STM.atomically                             $ STM.readTMVar mvar                     case result of                         Left exception -> throwIO exception                         Right value -> pure value-        UpdateCache entries -> withSpan "singleflight.update_cache" do+        UpdateCache entries -> do             STM.atomically $ do                 forM_ entries $ \(key, value) -> do                     existing <- Map.lookup key cache@@ -136,5 +128,5 @@                             -- No in-flight computation: create fresh TMVar with value                             tmvar <- STM.newTMVar (Right value)                             Map.insert tmvar key cache-        RemoveFromCache keys -> withSpan "singleflight.remove_from_cache" do+        RemoveFromCache keys -> do             STM.atomically $ forM_ keys $ \key -> Map.delete key cache
− src/Atelier/Effects/Conc/Traced.hs
@@ -1,86 +0,0 @@--- | Tracing integration for the 'Conc' effect.------ Provides interpreters and interposers that automatically propagate--- OpenTelemetry trace context across thread boundaries using span links.-module Atelier.Effects.Conc.Traced-    ( -- * Interpreters-      runConc-    , runConcByConfig-    , runConcTraced--      -- * Interposers-    , withConcTracingLinks-    )-where--import Effectful (IOE, raise, withEffToIO)-import Effectful.Concurrent (Concurrent)-import Effectful.Dispatch.Dynamic (interpose, localLend, localUnlift, passthrough)-import Effectful.Reader.Static (Reader, asks)--import Ki qualified--import Atelier.Effects.Conc (Conc (..), Scope (..), concStrat, fork, forkTry, fork_, runConcBase)-import Atelier.Effects.Monitoring.Tracing (Tracing, TracingConfig (..))--import Atelier.Effects.Conc qualified as Conc-import Atelier.Effects.Monitoring.Tracing qualified as Tracing----- | Run 'Conc' effect with automatic trace context propagation in a new scope.-runConc :: (Concurrent :> es, IOE :> es, Tracing :> es) => Eff (Conc : es) a -> Eff es a-runConc eff = withEffToIO concStrat $ \unlift ->-    Ki.scoped $ \scope ->-        unlift $ runConcTraced (Scope scope) eff----- | Run 'Conc' effect, selecting the interpreter based on tracing config.------ If tracing is enabled, uses 'runConc' for automatic span link propagation.--- If tracing is disabled, falls back to 'Conc.runConc' to skip the overhead.-runConcByConfig-    :: ( Concurrent :> es-       , IOE :> es-       , Reader TracingConfig :> es-       , Tracing :> es-       )-    => Eff (Conc : es) a -> Eff es a-runConcByConfig eff = do-    tracingEnabled <- asks @TracingConfig (.enabled)-    if tracingEnabled then runConc eff else Conc.runConc eff----- | Run 'Conc' effect with automatic trace context propagation.------ All fork variants ('fork', 'fork_', 'forkTry') use span links, i.e. forked--- threads start a fresh trace whose root spans carry a link back to the--- originating span.-runConcTraced-    :: ( Concurrent :> es-       , IOE :> es-       , Tracing :> es-       )-    => Scope -> Eff (Conc : es) a -> Eff es a-runConcTraced scope = runConcBase scope . withConcTracingLinks----- | Intercept fork operations and wrap the forked action with span link propagation.------ Re-dispatches to the underlying 'Conc' handler via 'fork', 'fork_', 'forkTry'.-withConcTracingLinks :: (Conc :> es, Tracing :> es) => Eff es a -> Eff es a-withConcTracingLinks = interpose \env -> \case-    Fork action -> tracedFork env action fork-    Fork_ action -> tracedFork env action fork_-    ForkTry action -> tracedFork env action forkTry-    other -> passthrough env other-  where-    tracedFork env action kiOp = do-        parentCtx <- Tracing.getSpanContext-        localUnlift env concStrat \unliftEff ->-            localLend @'[Tracing] env concStrat \lend ->-                kiOp-                    $ unliftEff-                        . lend-                        . Tracing.withLinkPropagation parentCtx-                        . raise @Tracing-                    $ action
src/Atelier/Effects/Env.hs view
@@ -5,6 +5,7 @@ module Atelier.Effects.Env     ( Env     , getEnvironment+    , lookupEnv     , runEnv     , runEnvConst     ) where@@ -13,6 +14,7 @@ import Effectful.Dispatch.Dynamic (interpret_) import Effectful.TH (makeEffect) +import Data.List qualified as List import System.Environment qualified as System  @@ -20,6 +22,8 @@ data Env :: Effect where     -- | The full environment as a list of @(name, value)@ pairs.     GetEnvironment :: Env m [(String, String)]+    -- | Pick out one value from the environment.+    LookupEnv :: String -> Env m (Maybe String)   makeEffect ''Env@@ -27,9 +31,13 @@  -- | Interpret 'Env' against the real process environment. runEnv :: (IOE :> es) => Eff (Env : es) a -> Eff es a-runEnv = interpret_ $ \GetEnvironment -> liftIO System.getEnvironment+runEnv = interpret_ $ \case+    GetEnvironment -> liftIO System.getEnvironment+    LookupEnv key -> liftIO $ System.lookupEnv key   -- | Interpret 'Env' with a fixed environment, for tests. runEnvConst :: [(String, String)] -> Eff (Env : es) a -> Eff es a-runEnvConst env = interpret_ $ \GetEnvironment -> pure env+runEnvConst env = interpret_ $ \case+    GetEnvironment -> pure env+    LookupEnv key -> pure $ List.lookup key env
src/Atelier/Effects/FileSystem.hs view
@@ -12,9 +12,12 @@     , followFile     , doesFileExist     , doesPathExist+    , doesDirectoryExist     , listDirectory     , createDirectoryIfMissing     , removeFile+    , writeFileBS+    , writeFileLBS     , canonicalizePath     , getCurrentDirectory     , getXdgRuntimeDir@@ -51,17 +54,23 @@     -- | Read a file's full contents strictly.     ReadFileBs :: FilePath -> FileSystem m ByteString     -- | Read a file from the given byte offset to the end, lazily.-    ReadFileLbsFrom :: FilePath -> FileOffset -> FileSystem m LBS.ByteString+    ReadFileLbsFrom :: FilePath -> FileOffset -> FileSystem m LByteString     -- | Does a regular file exist at the path?     DoesFileExist :: FilePath -> FileSystem m Bool     -- | Does anything (file or directory) exist at the path?     DoesPathExist :: FilePath -> FileSystem m Bool+    -- | Does a directory exist at the path?+    DoesDirectoryExist :: FilePath -> FileSystem m Bool     -- | List the entries of a directory.     ListDirectory :: FilePath -> FileSystem m [FilePath]     -- | Create a directory. The 'Bool' requests creation of missing parents.     CreateDirectoryIfMissing :: Bool -> FilePath -> FileSystem m ()     -- | Delete a file.     RemoveFile :: FilePath -> FileSystem m ()+    -- | Write a 'ByteString' to a file path.+    WriteFileBS :: FilePath -> ByteString -> FileSystem m ()+    -- | Write a (lazy) 'LByteString' to a file path.+    WriteFileLBS :: FilePath -> LByteString -> FileSystem m ()     -- | Resolve a path to a canonical, absolute form.     CanonicalizePath :: FilePath -> FileSystem m FilePath     -- | The process's current working directory.@@ -74,7 +83,7 @@   -- | Read a file's full contents lazily (equivalent to reading from offset 0).-readFileLbs :: (FileSystem :> es) => FilePath -> Eff es LBS.ByteString+readFileLbs :: (FileSystem :> es) => FilePath -> Eff es LByteString readFileLbs path = readFileLbsFrom path 0  @@ -106,9 +115,12 @@                 (readFdFrom offset)     DoesFileExist path -> liftIO $ Dir.doesFileExist path     DoesPathExist path -> liftIO $ Dir.doesPathExist path+    DoesDirectoryExist path -> liftIO $ Dir.doesDirectoryExist path     ListDirectory path -> liftIO $ Dir.listDirectory path     CreateDirectoryIfMissing p path -> liftIO $ Dir.createDirectoryIfMissing p path     RemoveFile path -> liftIO $ Dir.removeFile path+    WriteFileBS path bytes -> liftIO $ BS.writeFile path bytes+    WriteFileLBS path bytes -> liftIO $ LBS.writeFile path bytes     CanonicalizePath path -> liftIO $ Dir.canonicalizePath path     GetCurrentDirectory -> liftIO Dir.getCurrentDirectory     GetXdgRuntimeDir -> liftIO $ fromMaybe "/tmp" <$> lookupEnv "XDG_RUNTIME_DIR"@@ -122,9 +134,12 @@     ReadFileLbsFrom _ _ -> pure mempty     DoesFileExist _ -> pure False     DoesPathExist _ -> pure False+    DoesDirectoryExist _ -> pure False     ListDirectory _ -> pure []     CreateDirectoryIfMissing _ _ -> pure ()     RemoveFile _ -> pure ()+    WriteFileBS _ _ -> pure ()+    WriteFileLBS _ _ -> pure ()     CanonicalizePath path -> pure path     GetCurrentDirectory -> pure "."     GetXdgRuntimeDir -> pure "/tmp"@@ -150,9 +165,12 @@             $ contents     DoesFileExist fp -> gets $ M.member fp     DoesPathExist fp -> gets $ any (\p -> fp `isPrefixOf` p && fp /= p) . M.keys+    DoesDirectoryExist fp -> gets $ any (\p -> fp `isPrefixOf` p) . M.keys     ListDirectory fp -> gets $ filter (\p -> fp `isPrefixOf` p && fp /= p) . M.keys     CreateDirectoryIfMissing _ _ -> pure ()     RemoveFile fp -> modify $ M.delete fp+    WriteFileBS fp bytes -> modify $ M.insert fp bytes+    WriteFileLBS fp bytes -> modify $ M.insert fp $ toStrict bytes     CanonicalizePath fp -> pure fp     GetCurrentDirectory -> pure "/"     GetXdgRuntimeDir -> pure "/tmp"
− src/Atelier/Effects/Monitoring/Metrics.hs
@@ -1,125 +0,0 @@--- | Metrics effect for Prometheus metrics collection.------ Provides operations for tracking application metrics.------ == Basic Usage------ @--- myComponent :: (Metrics :> es) => Eff es ()--- myComponent = do---     gaugeSet "hoard_connected_peers" 5.0---     counterInc "hoard_blocks_received_total"--- @------ == Available Metric Types------ * Gauges: Point-in-time values that can go up or down--- * Counters: Monotonically increasing values--- * Histograms: Distributions with buckets for measuring durations-module Atelier.Effects.Monitoring.Metrics-    ( -- * Effect-      Metrics--      -- * Gauge Operations-    , gaugeSet-    , gaugeInc-    , gaugeDec--      -- * Counter Operations-    , counterInc-    , counterAdd--      -- * Histogram Operations-    , histogramObserve-    , withHistogramTiming--      -- * Export Operations-    , exportMetrics--      -- * Interpreters-    , runMetrics-    , runMetricsNoOp-    ) where--import Data.Time.Clock (diffUTCTime)-import Effectful (Effect, IOE)-import Effectful.Dispatch.Dynamic (interpret, interpretWith, localSeqUnlift)-import Effectful.TH (makeEffect)--import Prometheus qualified as Prom-import Prometheus.Metric.GHC qualified as GHC--import Atelier.Effects.Clock (Clock, currentTime)-import Atelier.Effects.Monitoring.Tracing (Tracing, withSpan)--import Atelier.Effects.Monitoring.Metrics.Registry qualified as Registry----- | Metrics effect for tracking application metrics-data Metrics :: Effect where-    -- | Set a named gauge to a specific value.-    GaugeSet :: Text -> Double -> Metrics m ()-    -- | Increment a named gauge by 1.-    GaugeInc :: Text -> Metrics m ()-    -- | Decrement a named gauge by 1.-    GaugeDec :: Text -> Metrics m ()-    -- | Increment a named counter by 1.-    CounterInc :: Text -> Metrics m ()-    -- | Add a value to a named counter.-    CounterAdd :: Text -> Double -> Metrics m ()-    -- | Observe a value in a named histogram.-    HistogramObserve :: Text -> Double -> Metrics m ()-    -- | Time an action and record its duration to a histogram metric-    WithHistogramTiming :: Text -> m a -> Metrics m a-    -- | Export all collected metrics in Prometheus text format.-    ExportMetrics :: Metrics m Text---makeEffect ''Metrics----- | Run the Metrics effect with prometheus-client------ Initializes the metric registry and registers GHC metrics automatically.-runMetrics-    :: forall es a-     . (Clock :> es, IOE :> es, Tracing :> es)-    => Eff (Metrics : es) a-    -> Eff es a-runMetrics action = do-    -- Initialize metrics registry and register GHC metrics--    handles <- withSpan "metrics.setup" do-        void $ liftIO $ Prom.register GHC.ghcMetrics-        liftIO Registry.initMetricHandles--    interpretWith action \env -> \case-        GaugeSet name value -> withSpan "metrics.gauge_set" $ liftIO $ Registry.setGauge handles name value-        GaugeInc name -> withSpan "metrics.gauge_inc" $ liftIO $ Registry.incGauge handles name-        GaugeDec name -> withSpan "metrics.gauge_dec" $ liftIO $ Registry.decGauge handles name-        CounterInc name -> withSpan "metrics.counter_inc" $ liftIO $ Registry.incCounter handles name-        CounterAdd name value -> withSpan "metrics.counter_add" $ liftIO $ Registry.addCounter handles name value-        HistogramObserve name value -> withSpan "metrics.histogram_observe" $ liftIO $ Registry.observeHistogram handles name value-        WithHistogramTiming metricName eff -> do-            start <- currentTime-            result <- localSeqUnlift env \unlift -> unlift eff-            end <- currentTime-            let duration = realToFrac $ diffUTCTime end start-            withSpan "metrics.with_histogram_timing.histogram_observe"-                $ liftIO-                $ Registry.observeHistogram handles metricName duration-            pure result-        ExportMetrics -> withSpan "metrics.export_metrics" $ liftIO $ decodeUtf8 <$> Prom.exportMetricsAsText----- | No-op interpreter that discards all metrics operations-runMetricsNoOp :: Eff (Metrics : es) a -> Eff es a-runMetricsNoOp = interpret \env -> \case-    GaugeSet _ _ -> pure ()-    GaugeInc _ -> pure ()-    GaugeDec _ -> pure ()-    CounterInc _ -> pure ()-    CounterAdd _ _ -> pure ()-    HistogramObserve _ _ -> pure ()-    WithHistogramTiming _ eff -> localSeqUnlift env \unlift -> unlift eff-    ExportMetrics -> pure ""
− src/Atelier/Effects/Monitoring/Metrics/Registry.hs
@@ -1,119 +0,0 @@--- | Metric registry for managing Prometheus metrics.------ The actual implementation of the `runMetrics` handler.-module Atelier.Effects.Monitoring.Metrics.Registry-    ( MetricHandles-    , initMetricHandles-    , setGauge-    , incGauge-    , decGauge-    , incCounter-    , addCounter-    , observeHistogram-    ) where--import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)--import Data.Map.Strict qualified as Map-import Prometheus qualified as Prom----- | Mutable handles to the registered Prometheus metrics, keyed by name and--- created on first use.-data MetricHandles = MetricHandles-    { gauges :: IORef (Map Text Prom.Gauge)-    , counters :: IORef (Map Text Prom.Counter)-    , histograms :: IORef (Map Text Prom.Histogram)-    }----- | Initialize empty metric handles-initMetricHandles :: IO MetricHandles-initMetricHandles = do-    MetricHandles-        <$> newIORef mempty-        <*> newIORef mempty-        <*> newIORef mempty----- | Set a gauge to a specific value-setGauge :: MetricHandles -> Text -> Double -> IO ()-setGauge handles name value = do-    g <- getOrCreateGauge handles name-    Prom.setGauge g value----- | Increment a gauge by 1-incGauge :: MetricHandles -> Text -> IO ()-incGauge handles name = do-    g <- getOrCreateGauge handles name-    Prom.incGauge g----- | Decrement a gauge by 1-decGauge :: MetricHandles -> Text -> IO ()-decGauge handles name = do-    g <- getOrCreateGauge handles name-    Prom.decGauge g----- | Increment a counter by 1-incCounter :: MetricHandles -> Text -> IO ()-incCounter handles name = do-    c <- getOrCreateCounter handles name-    Prom.incCounter c----- | Add a value to a counter-addCounter :: MetricHandles -> Text -> Double -> IO ()-addCounter handles name value = do-    c <- getOrCreateCounter handles name-    void $ Prom.addCounter c value----- | Observe a value in a histogram-observeHistogram :: MetricHandles -> Text -> Double -> IO ()-observeHistogram handles name value = do-    h <- getOrCreateHistogram handles name-    Prom.observe h value----- | Get or create a gauge metric-getOrCreateGauge :: MetricHandles -> Text -> IO Prom.Gauge-getOrCreateGauge handles name = do-    gaugeMap <- readIORef handles.gauges-    case Map.lookup name gaugeMap of-        Just g -> pure g-        Nothing -> do-            g <- Prom.register $ Prom.gauge (Prom.Info name "")-            atomicModifyIORef' handles.gauges $ \m ->-                (Map.insert name g m, ())-            pure g----- | Get or create a counter metric-getOrCreateCounter :: MetricHandles -> Text -> IO Prom.Counter-getOrCreateCounter handles name = do-    counterMap <- readIORef handles.counters-    case Map.lookup name counterMap of-        Just c -> pure c-        Nothing -> do-            c <- Prom.register $ Prom.counter (Prom.Info name "")-            atomicModifyIORef' handles.counters $ \m ->-                (Map.insert name c m, ())-            pure c----- | Get or create a histogram metric-getOrCreateHistogram :: MetricHandles -> Text -> IO Prom.Histogram-getOrCreateHistogram handles name = do-    histogramMap <- readIORef handles.histograms-    case Map.lookup name histogramMap of-        Just h -> pure h-        Nothing -> do-            -- Default buckets for duration metrics: 1ms, 10ms, 100ms, 1s, 10s-            let buckets = [0.001, 0.01, 0.1, 1.0, 10.0]-            h <- Prom.register $ Prom.histogram (Prom.Info name "") buckets-            atomicModifyIORef' handles.histograms $ \m ->-                (Map.insert name h m, ())-            pure h
− src/Atelier/Effects/Monitoring/Metrics/Server.hs
@@ -1,50 +0,0 @@--- | Effect for running the Prometheus metrics HTTP server.------ Wraps a Warp server that exposes the current metrics registry over HTTP, so--- callers start it through the effect system rather than raw 'IO'.-module Atelier.Effects.Monitoring.Metrics.Server-    ( -- * Effect-      MetricsServer--      -- * Operations-    , runMetricsServer--      -- * Interpreters-    , runMetricsServerIO-    ) where--import Effectful (Effect, IOE)-import Effectful.Dispatch.Dynamic (interpret_)-import Effectful.TH (makeEffect)-import Network.HTTP.Types (status200)-import Network.Wai (Application, responseLBS)--import Network.Wai.Handler.Warp qualified as Warp-import Prometheus qualified as Prom----- | Running the Prometheus metrics HTTP server.-data MetricsServer :: Effect where-    -- | Start an HTTP server that exposes Prometheus metrics at any path on the-    -- given port. Blocks until the server stops, which normally only happens on-    -- error; intended to be run in a background thread.-    RunMetricsServer :: Int -> MetricsServer m ()---makeEffect ''MetricsServer----- | Interpret 'MetricsServer' by running a real Warp HTTP server.-runMetricsServerIO :: (IOE :> es) => Eff (MetricsServer : es) a -> Eff es a-runMetricsServerIO = interpret_ \case-    RunMetricsServer port -> liftIO $ Warp.run port metricsApp---metricsApp :: Application-metricsApp _req respond = do-    payload <- Prom.exportMetricsAsText-    respond-        $ responseLBS-            status200-            [("Content-Type", "text/plain; version=0.0.4; charset=utf-8")]-            payload
− src/Atelier/Effects/Monitoring/Tracing.hs
@@ -1,293 +0,0 @@--- | Tracing effect for distributed tracing with OpenTelemetry.------ Provides operations for creating spans, adding attributes, and propagating trace context.------ == Basic Usage------ @--- myHandler :: (Tracing :> es) => Eff es ()--- myHandler = do---     withSpan "api.create_user" $ do---         addAttribute "user.id" "123"---         -- ... do work ...---         setStatus Ok--- @-module Atelier.Effects.Monitoring.Tracing-    ( -- * Effect-      Tracing (..)--      -- * Span Operations-    , withSpan-    , withSpanLinked-    , withLinkPropagation-    , addAttribute-    , addEvent-    , setStatus-    , getSpanContext-    , OT.ToAttribute (..)-    , Attr (..)-    , ToAttributeShow (..)--      -- * Interpreters-    , runTracing-    , runTracingFromConfig-    , runTracingNoOp--      -- * Configuration-    , TracingConfig (..)--      -- * Re-exports-    , SpanStatus (..)-    , SpanContext-    ) where--import Data.Aeson (FromJSON, ToJSON)-import Data.Default (Default (..))-import Effectful (Effect, IOE, Limit (..), Persistence (..), UnliftStrategy (..))-import Effectful.Dispatch.Dynamic (interposeWith, interpret, interpretWith, localSeqUnlift, localUnlift)-import Effectful.Exception (bracket, onException)-import Effectful.Reader.Static (Reader, ask)-import Effectful.TH (makeEffect)--import Data.HashMap.Strict qualified as HashMap-import OpenTelemetry.Context qualified as Context-import OpenTelemetry.Context.ThreadLocal qualified as ThreadLocal-import OpenTelemetry.Trace qualified as OT-import OpenTelemetry.Trace.Core qualified as OT--import Atelier.Effects.Timeout (Timeout, timeout)-import Atelier.Time (Second)-import Atelier.Types.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults (WithDefaults (..))--import Atelier.Effects.Monitoring.Tracing.Provider qualified as Provider----- | Tracing configuration for OpenTelemetry-data TracingConfig = TracingConfig-    { enabled :: Bool-    -- ^ Enable tracing-    , serviceName :: Text-    -- ^ Service name for traces-    , otlpEndpoint :: Text-    -- ^ OTLP endpoint (e.g., "http://localhost:4318")-    }-    deriving stock (Eq, Generic, Show)-    deriving (ToJSON) via QuietSnake TracingConfig-    deriving (FromJSON) via WithDefaults (QuietSnake TracingConfig)---instance Default TracingConfig where-    def =-        TracingConfig-            { enabled = False-            , serviceName = "hoard"-            , otlpEndpoint = "http://localhost:4318"-            }----- | Span status for indicating success or failure-data SpanStatus-    = -- | The operation completed successfully.-      Ok-    | -- | The operation failed, with an explanatory message.-      Error Text-    deriving stock (Eq, Show)----- | Opaque span context for correlating traces with metrics-type SpanContext = OT.SpanContext----- | Tracing effect for distributed tracing-data Tracing :: Effect where-    -- | Execute an action within a named span (bracket-style, automatic cleanup)-    WithSpan :: Text -> m a -> Tracing m a-    -- | Execute an action within a named span with links to other span contexts-    WithSpanLinked :: Text -> [SpanContext] -> m a -> Tracing m a-    -- | Add an attribute to the current span-    AddAttribute :: (OT.ToAttribute attr) => Text -> attr -> Tracing m ()-    -- | Add an event to the current span-    AddEvent :: (OT.ToAttribute attr) => Text -> [(Text, attr)] -> Tracing m ()-    -- | Set the status of the current span-    SetStatus :: SpanStatus -> Tracing m ()-    -- | Get the current span context (for exemplars)-    GetSpanContext :: Tracing m (Maybe SpanContext)-    -- | Get the current OpenTelemetry context (internal use)-    GetCurrentContext :: Tracing m Context.Context----- | Useful to create a heterogeneous list of attribute values. These two are equivalent:------ @--- addEvent "foo" [OT.toAttribute 1, OT.toAttribute "foo"]--- addEvent "foo" [Attr 1, Attr "foo"]--- @-data Attr where-    Attr :: (OT.ToAttribute a) => a -> Attr---instance OT.ToAttribute Attr where-    toAttribute (Attr a) = OT.toAttribute a----- | Wrapper that turns any 'Show'able value into an OpenTelemetry attribute via--- its 'Show' instance. Derive an attribute instance @via 'ToAttributeShow' T@,--- or wrap a value directly.-newtype ToAttributeShow a = ToAttributeShow-    { getToAttributeShow :: a-    }---instance (Show a) => OT.ToPrimitiveAttribute (ToAttributeShow a) where-    toPrimitiveAttribute = OT.TextAttribute . show . getToAttributeShow---instance (Show a) => OT.ToAttribute (ToAttributeShow a)---makeEffect ''Tracing----- | Run an action with automatic span link propagation for fire-and-forget forks.------ Any span created with no current parent (i.e., a root span) will automatically--- receive a link to @parentCtx@. Nested spans inside those are unaffected — they--- already have a parent and follow normal child semantics.------ This avoids the trace growth problem caused by parent-child propagation in--- long-running loops: each loop iteration's work becomes its own trace, linked--- back to the originating span rather than piling spans onto a single trace.-withLinkPropagation :: (Tracing :> es) => Maybe SpanContext -> Eff es a -> Eff es a-withLinkPropagation Nothing action = action-withLinkPropagation (Just parentSpanCtx) action =-    interposeWith action $ \env -> \case-        WithSpan name m -> do-            currentCtx <- getCurrentContext-            localUnlift env (ConcUnlift Persistent Unlimited) $ \unlift ->-                case Context.lookupSpan currentCtx of-                    Nothing -> withSpanLinked name [parentSpanCtx] (unlift m)-                    Just _ -> withSpan name (unlift m)-        WithSpanLinked name ctxs m ->-            localUnlift env (ConcUnlift Persistent Unlimited) $ \unlift ->-                withSpanLinked name ctxs (unlift m)-        AddAttribute key val -> addAttribute key val-        AddEvent name attrs -> addEvent name attrs-        SetStatus status -> setStatus status-        GetSpanContext -> getSpanContext-        GetCurrentContext -> getCurrentContext----- | Run the Tracing effect with OpenTelemetry------ Initializes the tracer provider and manages span lifecycle.-runTracing-    :: (IOE :> es, Timeout :> es)-    => Bool-    -- ^ Tracing enabled flag-    -> Text-    -- ^ Service name-    -> Text-    -- ^ OTLP endpoint-    -> Eff (Tracing : es) a-    -> Eff es a-runTracing enabled serviceName otlpEndpoint action-    | not enabled = runTracingNoOp action-    | otherwise =-        bracket-            (liftIO $ Provider.initTracingState serviceName otlpEndpoint)-            (\tracingState -> void $ timeout (3 :: Second) $ liftIO $ Provider.shutdownTracingState tracingState)-            $ \tracingState -> interpretWith action $ \env -> \case-                WithSpan spanName innerAction -> localSeqUnlift env $ \unlift -> do-                    currentCtx <- liftIO ThreadLocal.getContext-                    newSpan <- liftIO $ OT.createSpan tracingState.tracer currentCtx spanName OT.defaultSpanArguments-                    let newCtx = Context.insertSpan newSpan currentCtx-                    oldCtx <- liftIO $ ThreadLocal.attachContext newCtx-                    innerResult <--                        unlift innerAction `onException` do-                            liftIO $ OT.setStatus newSpan (OT.Error "Exception occurred")-                    -- Restore the old context-                    liftIO $ void $ case oldCtx of-                        Just ctx -> ThreadLocal.attachContext ctx-                        Nothing -> ThreadLocal.detachContext-                    liftIO $ OT.endSpan newSpan Nothing-                    pure innerResult-                WithSpanLinked spanName linkedContexts innerAction -> localSeqUnlift env $ \unlift -> do-                    currentCtx <- liftIO ThreadLocal.getContext-                    let spanArgs =-                            OT.defaultSpanArguments-                                { OT.links = map (\ctx -> OT.NewLink ctx mempty) linkedContexts-                                }-                    newSpan <- liftIO $ OT.createSpan tracingState.tracer currentCtx spanName spanArgs-                    let newCtx = Context.insertSpan newSpan currentCtx-                    oldCtx <- liftIO $ ThreadLocal.attachContext newCtx-                    innerResult <--                        unlift innerAction `onException` do-                            liftIO $ OT.setStatus newSpan (OT.Error "Exception occurred")--                    -- Restore the old context-                    liftIO $ void $ case oldCtx of-                        Just ctx -> ThreadLocal.attachContext ctx-                        Nothing -> ThreadLocal.detachContext-                    liftIO $ OT.endSpan newSpan Nothing-                    pure innerResult-                AddAttribute key value -> do-                    currentCtx <- liftIO ThreadLocal.getContext-                    case Context.lookupSpan currentCtx of-                        Just currentSpan ->-                            liftIO $ OT.addAttribute currentSpan key (OT.toAttribute value)-                        Nothing ->-                            -- No active span, ignore-                            pure ()-                AddEvent eventName attributes -> do-                    currentCtx <- liftIO ThreadLocal.getContext-                    case Context.lookupSpan currentCtx of-                        Just currentSpan -> do-                            let attrMap = HashMap.fromList $ map (\(k, v) -> (k, OT.toAttribute v)) attributes-                            let event = OT.NewEvent eventName attrMap Nothing-                            liftIO $ OT.addEvent currentSpan event-                        Nothing ->-                            -- No active span, ignore-                            pure ()-                SetStatus status -> do-                    currentCtx <- liftIO ThreadLocal.getContext-                    case Context.lookupSpan currentCtx of-                        Just currentSpan ->-                            liftIO $ case status of-                                Ok -> OT.setStatus currentSpan OT.Ok-                                Error msg -> OT.setStatus currentSpan (OT.Error msg)-                        Nothing ->-                            -- No active span, ignore-                            pure ()-                GetSpanContext -> do-                    currentCtx <- liftIO ThreadLocal.getContext-                    case Context.lookupSpan currentCtx of-                        Just currentSpan -> do-                            spanCtx <- liftIO $ OT.getSpanContext currentSpan-                            pure $ Just spanCtx-                        Nothing -> pure Nothing-                GetCurrentContext -> liftIO ThreadLocal.getContext----- | Run the Tracing effect with config from Reader------ Convenience wrapper that reads TracingConfig from the Reader effect.-runTracingFromConfig-    :: (IOE :> es, Reader TracingConfig :> es, Timeout :> es)-    => Eff (Tracing : es) a-    -> Eff es a-runTracingFromConfig action = do-    TracingConfig {enabled, serviceName, otlpEndpoint} <- ask-    runTracing enabled serviceName otlpEndpoint action----- | No-op interpreter that discards all tracing operations-runTracingNoOp :: Eff (Tracing : es) a -> Eff es a-runTracingNoOp = interpret $ \env -> \case-    WithSpan _ act -> localSeqUnlift env $ \unlift -> unlift act-    WithSpanLinked _ _ act -> localSeqUnlift env $ \unlift -> unlift act-    AddAttribute _ _ -> pure ()-    AddEvent _ _ -> pure ()-    SetStatus _ -> pure ()-    GetSpanContext -> pure Nothing-    GetCurrentContext -> pure Context.empty
− src/Atelier/Effects/Monitoring/Tracing/Provider.hs
@@ -1,70 +0,0 @@--- | Tracing provider for managing OpenTelemetry TracerProvider and span stack.------ The actual implementation backing the `runTracing` handler.-module Atelier.Effects.Monitoring.Tracing.Provider-    ( TracingState (..)-    , initTracingState-    , shutdownTracingState-    ) where--import System.Environment (setEnv)--import OpenTelemetry.Attributes qualified as OT-import OpenTelemetry.Trace qualified as OT----- | Handles to the initialised OpenTelemetry tracer provider and a tracer--- derived from it.-data TracingState = TracingState-    { tracerProvider :: OT.TracerProvider-    -- ^ The global tracer provider; flushed and shut down on teardown.-    , tracer :: OT.Tracer-    -- ^ The tracer used to create spans.-    }----- | Initialize tracing state using global tracer provider------ This uses initializeGlobalTracerProvider which reads configuration from--- environment variables (OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT)--- and properly sets up ID generation.-initTracingState-    :: Text-    -- ^ Service name-    -> Text-    -- ^ OTLP endpoint (e.g., "http://localhost:4318")-    -> IO TracingState-initTracingState serviceName otlpEndpoint = do-    -- Set environment variables for OpenTelemetry SDK-    setEnv "OTEL_SERVICE_NAME" (toString serviceName)-    setEnv "OTEL_EXPORTER_OTLP_ENDPOINT" (toString otlpEndpoint)--    -- Initialize the global tracer provider-    -- This reads from environment variables and sets up proper ID generation-    provider <- OT.initializeGlobalTracerProvider--    -- Create instrumentation library-    let instrumentationLibrary =-            OT.InstrumentationLibrary-                serviceName -- Library name-                "" -- Version (empty for now)-                "" -- Schema URL (empty for now)-                OT.emptyAttributes -- Attributes--    -- Get a tracer from the provider-    let tracerInstance = OT.makeTracer provider instrumentationLibrary OT.tracerOptions--    pure-        TracingState-            { tracerProvider = provider-            , tracer = tracerInstance-            }----- | Shutdown tracing state gracefully------ Flushes any remaining spans and cleans up resources.-shutdownTracingState :: TracingState -> IO ()-shutdownTracingState tracingState = do-    -- Force shutdown of the tracer provider to flush spans-    OT.shutdownTracerProvider tracingState.tracerProvider
src/Atelier/Effects/Publishing.hs view
@@ -1,69 +1,27 @@ module Atelier.Effects.Publishing     ( runPubSub-    , runPubSub_     ) where  import Data.Time (UTCTime)-import Effectful.Dispatch.Dynamic (interpret, interpretWith, interpretWith_, interpret_, localSeqUnlift)+import Effectful.Dispatch.Dynamic (interpret, interpret_, localSeqUnlift)  import Atelier.Effects.Chan (Chan) import Atelier.Effects.Clock (Clock)-import Atelier.Effects.Monitoring.Tracing (SpanContext, Tracing) import Atelier.Effects.Publishing.Pub (Pub (..)) import Atelier.Effects.Publishing.Sub (Sub (..))  import Atelier.Effects.Chan qualified as Chan import Atelier.Effects.Clock qualified as Clock-import Atelier.Effects.Monitoring.Tracing qualified as Tracing  --- | Internal wrapper for events with trace context-data TracedEvent event = TracedEvent-    { event :: event-    , timestamp :: UTCTime-    , publisherSpanContext :: Maybe SpanContext-    }-- -- | Runs 'Pub' and 'Sub' effects with an internal channel for a specific event--- type. Automatically captures span context from the publisher and creates--- linked spans in listeners.-runPubSub-    :: forall event es a-     . ( Chan :> es-       , Clock :> es-       , Tracing :> es-       )-    => Eff (Pub event : Sub event : es) a -> Eff es a-runPubSub action = do-    (inChan, _) <- Chan.newChan @(TracedEvent event)--    let handlePub eff = interpretWith_ eff \case-            Publish event -> do-                timestamp <- Clock.currentTime-                -- Capture the current span context from the publisher-                publisherSpanContext <- Tracing.getSpanContext-                Chan.writeChan inChan TracedEvent {event, timestamp, publisherSpanContext}--        handleSub eff = interpretWith eff \env -> \case-            ListenWith onSubscribed listener -> localSeqUnlift env \unlift -> do-                chan <- Chan.dupChan inChan-                unlift onSubscribed-                forever do-                    TracedEvent {event, timestamp, publisherSpanContext} <- Chan.readChan chan-                    Tracing.withLinkPropagation publisherSpanContext $ unlift $ listener timestamp event--    handleSub . handlePub $ action----- | Runs 'Pub' and 'Sub' effects with an internal channel for a specific event -- type.-runPubSub_+runPubSub     :: forall event es a      . (Chan :> es, Clock :> es)     => Eff (Pub event : Sub event : es) a -> Eff es a-runPubSub_ action = do+runPubSub action = do     (inChan, _) <- Chan.newChan @(UTCTime, event)     let handlePub = interpret_ \case             Publish event -> do
test/Unit/Atelier/Effects/Cache/SingleflightSpec.hs view
@@ -9,7 +9,6 @@ import Atelier.Effects.Cache.Singleflight (Singleflight, runSingleflight, updateCache, withCache) import Atelier.Effects.Conc (Conc, runConc) import Atelier.Effects.Delay (Delay, runDelay)-import Atelier.Effects.Monitoring.Tracing (Tracing, runTracingNoOp) import Atelier.Time (Millisecond) import Atelier.Types.Semaphore (Semaphore) @@ -26,13 +25,12 @@  -- | Run a Singleflight test with execution counter runSingleflightTest-    :: Eff [Singleflight Int Int, State Int, Delay, Tracing, Conc, Concurrent, IOE] a+    :: Eff [Singleflight Int Int, State Int, Delay, Conc, Concurrent, IOE] a     -> IO (a, Int) runSingleflightTest action =     runEff         . runConcurrent         . runConc-        . runTracingNoOp         . runDelay         . runState @Int 0         . runSingleflight @Int @Int
test/Unit/Atelier/Effects/Conc/TeardownStressSpec.hs view
@@ -27,7 +27,6 @@ import Atelier.Effects.Chan (Chan, runChan) import Atelier.Effects.Clock (Clock, runClock) import Atelier.Effects.Conc (Conc, fork, fork_, runConc, scoped)-import Atelier.Effects.Monitoring.Tracing (Tracing, runTracingNoOp) import Atelier.Effects.Publishing (runPubSub) import Atelier.Effects.Publishing.Pub (Pub) import Atelier.Effects.Publishing.Sub (Sub)@@ -122,5 +121,5 @@   -- | Mirrors 'IteratorSpec.runTest' — the full effect stack the real victim runs on.-runIterTest :: Eff '[Pub Int, Sub Int, Chan, Clock, Tracing, Conc, Concurrent, IOE] a -> IO a-runIterTest = runEff . runConcurrent . runConc . runTracingNoOp . runClock . runChan . runPubSub @Int+runIterTest :: Eff '[Pub Int, Sub Int, Chan, Clock, Conc, Concurrent, IOE] a -> IO a+runIterTest = runEff . runConcurrent . runConc . runClock . runChan . runPubSub @Int
− test/Unit/Atelier/Effects/Conc/TracedSpec.hs
@@ -1,131 +0,0 @@-module Unit.Atelier.Effects.Conc.TracedSpec (spec_ConcTraced) where--import Control.Concurrent (newEmptyMVar, putMVar, takeMVar, threadDelay)-import Data.IORef-import Effectful (IOE, runEff)-import Effectful.Concurrent (runConcurrent)-import Effectful.Dispatch.Dynamic (interpret, localUnlift)-import OpenTelemetry.Common (TraceFlags (..))-import OpenTelemetry.Internal.Trace.Id (SpanId (..), TraceId (..))-import OpenTelemetry.Trace.TraceState (TraceState (..))-import Test.Hspec (Spec, describe, it, shouldReturn, shouldSatisfy)--import OpenTelemetry.Context qualified as Context-import OpenTelemetry.Context.ThreadLocal qualified as ThreadLocal-import OpenTelemetry.Trace.Core qualified as OT--import Atelier.Effects.Conc (await, concStrat, fork, fork_)-import Atelier.Effects.Conc.Traced (runConc)-import Atelier.Effects.Monitoring.Tracing (SpanContext, Tracing (..), withLinkPropagation, withSpan)---spec_ConcTraced :: Spec-spec_ConcTraced = do-    describe "withLinkPropagation" $ do-        it "passes through when no parent context" $ do-            ops <- newIORef []-            runEff . runTracingCapture ops-                $ withLinkPropagation Nothing-                $ withSpan "foo"-                $ pure ()-            readIORef ops `shouldReturn` [PlainSpan "foo"]--        it "converts root span to linked span when parent context given" $ do-            ops <- newIORef []-            runEff . runTracingCapture ops-                $ withLinkPropagation (Just fakeSpanCtx)-                $ withSpan "foo"-                $ pure ()-            readIORef ops `shouldReturn` [LinkedSpan "foo"]--        it "does not convert nested spans (they already have a parent)" $ do-            ops <- newIORef []-            runEff . runTracingCapture ops-                $ withSpan "outer"-                $ withLinkPropagation (Just fakeSpanCtx)-                $ withSpan "inner"-                $ pure ()-            readIORef ops `shouldReturn` [PlainSpan "outer", PlainSpan "inner"]--    describe "withConcTracingLinks" $ do-        it "does not link when forking outside any span" $ do-            ops <- newIORef []-            runEff . runConcurrent . runTracingCapture ops . runConc $ do-                t <- fork $ withSpan "child" $ pure ()-                await t-            readIORef ops `shouldReturn` [PlainSpan "child"]--        it "links forked thread's root span to the parent span" $ do-            ops <- newIORef []-            runEff . runConcurrent . runTracingCapture ops . runConc $ do-                withSpan "parent" $ do-                    t <- fork $ withSpan "child" $ pure ()-                    await t-            readIORef ops `shouldReturn` [PlainSpan "parent", LinkedSpan "child"]--        it "links fork_ thread's root span to the parent span" $ do-            ops <- newIORef []-            spanRecorded <- newEmptyMVar-            runEff . runConcurrent . runTracingCapture ops . runConc $ do-                withSpan "parent" $ do-                    fork_ $ do-                        withSpan "bg" $ pure ()-                        liftIO $ putMVar spanRecorded ()-                        liftIO $ forever $ threadDelay maxBound-                    liftIO $ takeMVar spanRecorded-            readIORef ops >>= (`shouldSatisfy` elem (LinkedSpan "bg"))-------------------------------------------------------------------------------------- Test Infrastructure-----------------------------------------------------------------------------------data SpanOp-    = PlainSpan Text-    | LinkedSpan Text-    deriving stock (Eq, Show)----- | A fake but valid span context for testing.-fakeSpanCtx :: SpanContext-fakeSpanCtx =-    OT.SpanContext-        (TraceFlags 0x01)-        False-        (TraceId "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1")-        (SpanId "\0\0\0\0\0\0\0\1")-        (TraceState [])----- | Test interpreter that records 'PlainSpan' vs 'LinkedSpan' operations.------ Uses real thread-local storage for context tracking so that forked threads--- correctly start with an empty context (as they would in production), allowing--- 'withLinkPropagation' to distinguish root spans from nested ones.-runTracingCapture-    :: (IOE :> es)-    => IORef [SpanOp]-    -> Eff (Tracing : es) a-    -> Eff es a-runTracingCapture log = interpret $ \env -> \case-    WithSpan name act -> do-        liftIO $ modifyIORef' log (<> [PlainSpan name])-        currentCtx <- liftIO ThreadLocal.getContext-        let newCtx = Context.insertSpan (OT.wrapSpanContext fakeSpanCtx) currentCtx-        oldCtx <- liftIO $ ThreadLocal.attachContext newCtx-        result <- localUnlift env concStrat $ \unlift -> unlift act-        liftIO $ void $ case oldCtx of-            Just ctx -> ThreadLocal.attachContext ctx-            Nothing -> ThreadLocal.detachContext-        pure result-    WithSpanLinked name _ act -> do-        liftIO $ modifyIORef' log (<> [LinkedSpan name])-        localUnlift env concStrat $ \unlift -> unlift act-    GetSpanContext -> do-        ctx <- liftIO ThreadLocal.getContext-        liftIO $ traverse OT.getSpanContext (Context.lookupSpan ctx)-    GetCurrentContext ->-        liftIO ThreadLocal.getContext-    AddAttribute _ _ -> pure ()-    AddEvent _ _ -> pure ()-    SetStatus _ -> pure ()
test/Unit/Atelier/Effects/IteratorSpec.hs view
@@ -8,7 +8,6 @@ import Atelier.Effects.Chan (Chan, runChan) import Atelier.Effects.Clock (Clock, runClock) import Atelier.Effects.Conc (Conc, fork, runConc)-import Atelier.Effects.Monitoring.Tracing (Tracing, runTracingNoOp) import Atelier.Effects.Publishing (runPubSub) import Atelier.Effects.Publishing.Pub (Pub) import Atelier.Effects.Publishing.Sub (Sub)@@ -110,12 +109,11 @@ -- Test Helpers -------------------------------------------------------------------------------- -runTest :: Eff '[Pub Int, Sub Int, Chan, Clock, Tracing, Conc, Concurrent, IOE] a -> IO a+runTest :: Eff '[Pub Int, Sub Int, Chan, Clock, Conc, Concurrent, IOE] a -> IO a runTest =     runEff         . runConcurrent         . runConc-        . runTracingNoOp         . runClock         . runChan         . runPubSub @Int
test/Unit/Atelier/Effects/PublishingSpec.hs view
@@ -9,7 +9,6 @@ import Atelier.Effects.Chan (Chan, runChan) import Atelier.Effects.Clock (Clock, runClock, runClockConst) import Atelier.Effects.Conc (Conc, runConc)-import Atelier.Effects.Monitoring.Tracing (Tracing, runTracingNoOp) import Atelier.Effects.Publishing (runPubSub) import Atelier.Effects.Publishing.Pub (Pub) import Atelier.Effects.Publishing.Sub (Sub)@@ -62,13 +61,12 @@ --------------------------------------------------------------------------------  runPubSubTest-    :: Eff '[Pub TestEvent, Sub TestEvent, Chan, Clock, Tracing, Conc, Concurrent, IOE] a+    :: Eff '[Pub TestEvent, Sub TestEvent, Chan, Clock, Conc, Concurrent, IOE] a     -> IO a runPubSubTest =     runEff         . runConcurrent         . runConc-        . runTracingNoOp         . runClock         . runChan         . runPubSub @TestEvent@@ -76,13 +74,12 @@  runPubSubTestWithClock     :: UTCTime-    -> Eff '[Pub TestEvent, Sub TestEvent, Chan, Clock, Tracing, Conc, Concurrent, IOE] a+    -> Eff '[Pub TestEvent, Sub TestEvent, Chan, Clock, Conc, Concurrent, IOE] a     -> IO a runPubSubTestWithClock t =     runEff         . runConcurrent         . runConc-        . runTracingNoOp         . runClockConst t         . runChan         . runPubSub @TestEvent