diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,24 @@
-## [_Unreleased_](https://github.com/freckle/freckle-otel/compare/v0.0.0.3...main)
+## [_Unreleased_](https://github.com/freckle/freckle-otel/compare/v0.1.0.0...main)
+
+## [v0.1.0.0](https://github.com/freckle/freckle-otel/compare/v0.0.0.3...v0.1.0.0)
+
+Drops support for GHC 9.2 and GHC 9.4. `hs-opentelemetry-sdk`'s OTLP exporter now depends on a
+`tls`/`crypton` stack too recent to satisfy against those two GHC versions' package sets. GHC 9.6
+is now the oldest supported version, which also drops support for `base` 4.16 and 4.17.
+
+Require `hs-opentelemetry-api` and `hs-opentelemetry-sdk` at `1.0.0.0` or later. A few behavior
+changes from that upgrade are visible through this package's own functions:
+
+- `withTracerProvider`'s shutdown now applies a default 5 second timeout instead of waiting
+  indefinitely
+- `inSpan`, and so `getCurrentSpanContext`, `getCurrentTraceId`, `addCurrentSpanAttributes`, and
+  `withTraceContext`, no longer see the current span when the `TracerProvider` has no
+  `SpanProcessor` configured (e.g. `OTEL_TRACES_EXPORTER=none`)
+- `extractContext`, `injectContext`, and `processWithContext` keep propagating trace context via
+  headers even when `OTEL_SDK_DISABLED=true`, where they previously became no-ops
+- the `traceparent` header `injectContext`/`processWithContext` write for a sampled root span now
+  has its trailing flags byte set to `03` (sampled, plus the new W3C Trace Context "random trace
+  ID" bit), where it was previously `00`
 
 ## [v0.0.0.3](https://github.com/freckle/freckle-otel/compare/v0.0.0.2...v0.0.0.3)
 
diff --git a/freckle-otel.cabal b/freckle-otel.cabal
--- a/freckle-otel.cabal
+++ b/freckle-otel.cabal
@@ -1,6 +1,6 @@
 cabal-version:      1.18
 name:               freckle-otel
-version:            0.0.0.3
+version:            0.1.0.0
 license:            MIT
 license-file:       LICENSE
 maintainer:         Freckle Education
@@ -45,23 +45,23 @@
         -Wno-safe -Wno-unsafe
 
     build-depends:
-        Blammo >=2.1.0.0,
-        aeson >=2.0.3.0,
-        base >=4.16.4.0 && <5,
-        bytestring >=0.11.4.0,
+        Blammo >=2.1.1.0,
+        aeson >=2.1.2.1,
+        base >=4.18.2.1 && <5,
+        bytestring >=0.11.5.3,
         case-insensitive >=1.2.1.0,
         errors >=2.3.0,
-        exceptions >=0.10.4,
-        faktory >=1.1.2.4,
-        hs-opentelemetry-api >=0.1.0.0,
-        hs-opentelemetry-sdk >=0.0.3.6,
-        http-client >=0.7.13.1,
+        exceptions >=0.10.7,
+        faktory >=1.1.3.0,
+        hs-opentelemetry-api >=1.0.0.0,
+        hs-opentelemetry-sdk >=1.0.0.0,
+        http-client >=0.7.17,
         http-conduit >=2.3.5,
-        http-types >=0.12.3,
-        lens >=5.1.1,
-        text >=1.2.5.0,
+        http-types >=0.12.4,
+        lens >=5.2.3,
+        text >=2.0.2,
         unliftio >=0.2.25.0,
-        unordered-containers >=0.2.19.1
+        unordered-containers >=0.2.20
 
     if impl(ghc >=9.8)
         ghc-options:
@@ -91,20 +91,20 @@
         -Wno-safe -Wno-unsafe -threaded -rtsopts -with-rtsopts=-N
 
     build-depends:
-        Blammo >=2.1.0.0,
-        base >=4.16.4.0 && <5,
-        exceptions >=0.10.4,
+        Blammo >=2.1.1.0,
+        base >=4.18.2.1 && <5,
+        exceptions >=0.10.7,
         freckle-env >=0.0.1.2,
         freckle-otel,
-        hs-opentelemetry-api >=0.1.0.0,
-        hs-opentelemetry-sdk >=0.0.3.6,
-        hspec >=2.9.7,
-        hspec-core >=2.9.7,
+        hs-opentelemetry-api >=1.0.0.0,
+        hs-opentelemetry-sdk >=1.0.0.0,
+        hspec >=2.11.10,
+        hspec-core >=2.11.10,
         hspec-expectations-lifted >=0.10.0,
-        http-types >=0.12.3,
-        lens >=5.1.1,
-        mtl >=2.2.2,
-        text >=1.2.5.0,
+        http-types >=0.12.4,
+        lens >=5.2.3,
+        mtl >=2.3.1,
+        text >=2.0.2,
         unliftio >=0.2.25.0
 
     if impl(ghc >=9.8)
diff --git a/library/Freckle/App/OpenTelemetry.hs b/library/Freckle/App/OpenTelemetry.hs
--- a/library/Freckle/App/OpenTelemetry.hs
+++ b/library/Freckle/App/OpenTelemetry.hs
@@ -19,11 +19,8 @@
 -- You may need to do this even if you don't plan to manually trace things, in
 -- order to satisfy the 'MonadTracer' constraint required by functions like
 -- 'runDB'. If you don't need this feature, and don't plan on running an
--- otel-collector, set @OTEL_TRACES_EXPORTER=none@ in the environment, which
--- makes all tracing a no-op.
---
--- In the future, it should be possible to use @OTEL_SDK_DISABLED@ for the same
--- purpose. See <https://github.com/iand675/hs-opentelemetry/issues/60>.
+-- otel-collector, set @OTEL_SDK_DISABLED=true@ in the environment, to
+-- disable any actual tracing.
 module Freckle.App.OpenTelemetry
   ( HasTracer (..)
   , Tracer
@@ -76,7 +73,7 @@
 import Data.Text.Encoding.Error qualified as T
 import OpenTelemetry.Context (lookupSpan)
 import OpenTelemetry.Context.ThreadLocal (getContext)
-import OpenTelemetry.Trace hiding (inSpan)
+import OpenTelemetry.Trace hiding (inSpan, withTracerProvider)
 import OpenTelemetry.Trace.Core (getSpanContext)
 import OpenTelemetry.Trace.Core qualified as Trace (SpanContext (..))
 import OpenTelemetry.Trace.Id
@@ -127,7 +124,7 @@
 withTracerProvider =
   bracket
     (liftIO initializeGlobalTracerProvider)
-    (liftIO . shutdownTracerProvider)
+    (void . liftIO . flip shutdownTracerProvider Nothing)
 
 getCurrentTraceId :: MonadIO m => m (Maybe TraceId)
 getCurrentTraceId = fmap Trace.traceId <$> getCurrentSpanContext
diff --git a/library/Freckle/App/OpenTelemetry/Context.hs b/library/Freckle/App/OpenTelemetry/Context.hs
--- a/library/Freckle/App/OpenTelemetry/Context.hs
+++ b/library/Freckle/App/OpenTelemetry/Context.hs
@@ -38,7 +38,14 @@
 import Network.HTTP.Types.Header (Header)
 import OpenTelemetry.Context (Context)
 import OpenTelemetry.Context.ThreadLocal (attachContext, getContext)
-import OpenTelemetry.Propagator (Propagator, extract, inject)
+import OpenTelemetry.Propagator
+  ( Propagator
+  , TextMap
+  , extract
+  , inject
+  , textMapFromList
+  , textMapToList
+  )
 import OpenTelemetry.Trace.Core
   ( getTracerProviderPropagators
   , getTracerTracerProvider
@@ -89,7 +96,7 @@
 extractContext a = do
   context <- getContext
   propagator <- getPropagator
-  updatedContext <- extract propagator (a ^. headersL) context
+  updatedContext <- extract propagator (headersToTextMap $ a ^. headersL) context
   void $ attachContext updatedContext
 
 -- | Inject our trace context into the given item's headers
@@ -98,12 +105,20 @@
 injectContext a = do
   context <- getContext
   propagator <- getPropagator
-  headers <- inject propagator context $ a ^. headersL
+  headers <-
+    textMapToHeaders
+      <$> inject propagator context (headersToTextMap $ a ^. headersL)
   pure $ a & headersL .~ headers
 
-getPropagator :: MonadTracer m => m (Propagator Context [Header] [Header])
+getPropagator :: MonadTracer m => m (Propagator Context TextMap TextMap)
 getPropagator =
   getTracerProviderPropagators . getTracerTracerProvider <$> getTracer
+
+headersToTextMap :: [Header] -> TextMap
+headersToTextMap = textMapFromList . map decode
+
+textMapToHeaders :: TextMap -> [Header]
+textMapToHeaders = map encode . textMapToList
 
 -- | Process an item (a request, a Job, etc) in a top-level span and context
 processWithContext
diff --git a/package.yaml b/package.yaml
--- a/package.yaml
+++ b/package.yaml
@@ -1,5 +1,5 @@
 name: freckle-otel
-version: 0.0.0.3
+version: 0.1.0.0
 maintainer: Freckle Education
 category: OpenTelemetry, Telemetry, Monitoring, Observability, Metrics
 github: freckle/freckle-otel
diff --git a/tests/Freckle/App/OpenTelemetry/ContextSpec.hs b/tests/Freckle/App/OpenTelemetry/ContextSpec.hs
--- a/tests/Freckle/App/OpenTelemetry/ContextSpec.hs
+++ b/tests/Freckle/App/OpenTelemetry/ContextSpec.hs
@@ -9,6 +9,7 @@
 import Blammo.Logging.Logger (Logger, newTestLogger)
 import Blammo.Logging.Setup (HasLogger (..))
 import Control.Lens (lens)
+import Control.Monad (void)
 import Control.Monad.IO.Class (MonadIO)
 import Data.List qualified as List
 import Data.Text (Text)
@@ -18,6 +19,8 @@
 import Freckle.App.OpenTelemetry.Context
 import GHC.Stack (HasCallStack)
 import Network.HTTP.Types.Header (Header)
+import OpenTelemetry.Exporter.Span (ExportResult (..), SpanExporter (..))
+import OpenTelemetry.Registry (registerSpanExporterFactoryIfAbsent)
 import OpenTelemetry.Trace.Core qualified as Trace
 import Test.Hspec (Spec, describe, it)
 import Test.Hspec.Expectations.Lifted
@@ -42,26 +45,42 @@
 loadApp f = do
   appLogger <- newTestLogger defaultLogSettings
 
+  -- A TracerProvider with no SpanProcessor never attaches its spans to
+  -- context, so getCurrentSpanContext wouldn't see them; register a no-op
+  -- exporter so one exists without actually exporting anywhere
+  void
+    $ registerSpanExporterFactoryIfAbsent "freckle-otel-test-noop"
+    $ pure noopSpanExporter
+
   withTracerProvider $ \tracerProvider -> do
     let appTracer = makeTracer tracerProvider "app" tracerOptions
     f App {..}
 
+noopSpanExporter :: SpanExporter
+noopSpanExporter =
+  SpanExporter
+    { spanExporterExport = const $ pure Success
+    , spanExporterShutdown = pure Trace.ShutdownSuccess
+    , spanExporterForceFlush = pure Trace.FlushSuccess
+    }
+
 spec :: Spec
 spec = withApp loadApp $ do
   describe "injectContext" $ do
     it "sets request headers from existing context" $ appExample @App $ do
       inSpan "example" defaultSpanArguments $ do
         spanContext <- assertCurrentSpanContext
+        headers <- injectContext ([] :: [Header])
 
-        let expectedTraceParent =
-              toTraceParent
-                (traceIdToHex $ Trace.traceId spanContext)
-                (spanIdToHex $ Trace.spanId spanContext)
+        let headerTraceParent = do
+              bs <- List.lookup "traceparent" headers
+              fromTraceParent $ T.decodeUtf8 bs
 
-        injectContext ([] :: [Header])
-          `shouldReturn` [ ("traceparent", T.encodeUtf8 expectedTraceParent)
-                         , ("tracestate", "")
-                         ]
+        fmap fst headerTraceParent
+          `shouldBe` Just (traceIdToHex $ Trace.traceId spanContext)
+        fmap snd headerTraceParent
+          `shouldBe` Just (spanIdToHex $ Trace.spanId spanContext)
+        List.lookup "tracestate" headers `shouldBe` Just ""
 
   describe "extractContext" $ do
     it "sets the context from the headers" $ appExample @App $ do
@@ -163,11 +182,9 @@
 toTraceParent traceId spanId = "00-" <> traceId <> "-" <> spanId <> "-00"
 
 fromTraceParent :: Text -> Maybe (Text, Text)
-fromTraceParent a = do
-  b <- T.stripPrefix "00-" a
-  c <- T.stripSuffix "-00" b
-  [traceId, spanId] <- Just $ T.splitOn "-" c
-  pure (traceId, spanId)
+fromTraceParent a = case T.splitOn "-" a of
+  [_version, traceId, spanId, _flags] -> Just (traceId, spanId)
+  _ -> Nothing
 
 expectationFailure :: (HasCallStack, MonadIO m) => String -> m a
 expectationFailure msg = Hspec.expectationFailure msg >> error "unreachable"
