diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog for settei-optparse-applicative
 
+## 0.2.0.0 — 2026-07-19
+
+- Breaking: replace `ExplainMode` and its parsers with `DiagnosticMode`, which also
+  supports `--check-config`, `--describe-config`, and `--describe-config-json`.
+- Add `schemaDiagnostic` and `resolutionDiagnostic` helpers for the source-free and
+  post-resolution diagnostic paths.
+
 ## 0.1.0.0 — 2026-07-18
 
 - Initial experimental release.
diff --git a/settei-optparse-applicative.cabal b/settei-optparse-applicative.cabal
--- a/settei-optparse-applicative.cabal
+++ b/settei-optparse-applicative.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.8
 name:            settei-optparse-applicative
-version:         0.1.0.0
+version:         0.2.0.0
 synopsis:        optparse-applicative sources for Settei
 description:
   Parse generic and named command-line overrides into provenance-aware Settei
@@ -40,7 +40,7 @@
     , containers            >=0.6.8   && <0.8
     , generic-lens          >=2.2     && <2.4
     , optparse-applicative  >=0.19    && <0.20
-    , settei                ==0.1.0.0
+    , settei                ==0.2.0.0
     , text                  >=2.1     && <2.2
 
 test-suite settei-optparse-applicative-tests
@@ -54,9 +54,9 @@
     , containers                   >=0.6.8   && <0.8
     , generic-lens                 >=2.2     && <2.4
     , optparse-applicative         >=0.19    && <0.20
-    , settei                       ==0.1.0.0
-    , settei-env                   ==0.1.0.0
-    , settei-optparse-applicative  ==0.1.0.0
+    , settei                       ==0.2.0.0
+    , settei-env                   ==0.2.0.0
+    , settei-optparse-applicative  ==0.2.0.0
     , tasty                        >=1.5     && <1.6
     , tasty-hunit                  >=0.10.2  && <0.11
     , text                         >=2.1     && <2.2
diff --git a/src/Settei/Optparse.hs b/src/Settei/Optparse.hs
--- a/src/Settei/Optparse.hs
+++ b/src/Settei/Optparse.hs
@@ -3,7 +3,7 @@
 -- Description: optparse-applicative parsers that produce ordered Settei sources.
 module Settei.Optparse
   ( CliOverride,
-    ExplainMode (..),
+    DiagnosticMode (..),
     SetteiOptions (..),
     cliOverride,
     cliOverrideKey,
@@ -12,11 +12,12 @@
     cliSources,
     configPathOptions,
     configPathOptionsWith,
-    explainModeOptions,
-    explainModeOptionsWith,
+    diagnosticModeOptions,
     namedOption,
     overrideOptions,
     overrideOptionsWith,
+    resolutionDiagnostic,
+    schemaDiagnostic,
     setteiOptions,
   )
 where
@@ -26,7 +27,7 @@
 import Data.List.NonEmpty qualified as NonEmpty
 import Data.Map.Strict qualified as Map
 import Data.Text qualified as Text
-import Options.Applicative (FlagFields, Mod, OptionFields, Parser)
+import Options.Applicative (Mod, OptionFields, Parser)
 import Options.Applicative qualified as Options
 import Settei
 import Settei.Prelude
@@ -42,15 +43,21 @@
   }
   deriving stock (Generic, Eq)
 
--- | Whether an application should print a configuration explanation.
-data ExplainMode = NoExplain | ExplainText | ExplainJson
+-- | Which configuration diagnostic, if any, an application should perform.
+data DiagnosticMode
+  = NoDiagnostic
+  | ExplainText
+  | ExplainJson
+  | CheckConfig
+  | DescribeConfigText
+  | DescribeConfigJson
   deriving stock (Generic, Eq, Ord, Show)
 
 -- | Reusable configuration and diagnostic command-line options.
 data SetteiOptions = SetteiOptions
   { configPaths :: ![FilePath],
     overrides :: ![CliOverride],
-    explainMode :: !ExplainMode
+    diagnosticMode :: !DiagnosticMode
   }
   deriving stock (Generic, Eq)
 
@@ -61,7 +68,7 @@
   deriving stock (Generic, Eq)
 
 newtype DiagnosticOptions = DiagnosticOptions
-  { explainMode :: ExplainMode
+  { diagnosticMode :: DiagnosticMode
   }
   deriving stock (Generic, Eq)
 
@@ -128,20 +135,31 @@
 configPathOptionsWith :: Mod OptionFields FilePath -> Parser [FilePath]
 configPathOptionsWith modifiers = Applicative.many (Options.strOption modifiers)
 
--- | Parse the default mutually exclusive explanation flags.
-explainModeOptions :: Parser ExplainMode
-explainModeOptions =
-  explainModeOptionsWith
-    (Options.long "explain-config" <> Options.help "Explain the resolved configuration as text")
-    (Options.long "explain-config-json" <> Options.help "Explain the resolved configuration as JSON")
+-- | Parse the default mutually exclusive configuration diagnostic flags.
+diagnosticModeOptions :: Parser DiagnosticMode
+diagnosticModeOptions =
+  Options.flag' ExplainText (Options.long "explain-config" <> Options.help "Explain the resolved configuration as text")
+    Applicative.<|> Options.flag' ExplainJson (Options.long "explain-config-json" <> Options.help "Explain the resolved configuration as JSON")
+    Applicative.<|> Options.flag' CheckConfig (Options.long "check-config" <> Options.help "Validate configuration and exit")
+    Applicative.<|> Options.flag' DescribeConfigText (Options.long "describe-config" <> Options.help "Print the static configuration schema")
+    Applicative.<|> Options.flag' DescribeConfigJson (Options.long "describe-config-json" <> Options.help "Print the static configuration schema as JSON")
+    Applicative.<|> pure NoDiagnostic
 
--- | Parse caller-named mutually exclusive text and JSON explanation flags.
-explainModeOptionsWith :: Mod FlagFields ExplainMode -> Mod FlagFields ExplainMode -> Parser ExplainMode
-explainModeOptionsWith textModifiers jsonModifiers =
-  Options.flag' ExplainText textModifiers
-    Applicative.<|> Options.flag' ExplainJson jsonModifiers
-    Applicative.<|> pure NoExplain
+-- | Render a diagnostic that must not load configuration sources.
+schemaDiagnostic :: DiagnosticMode -> Schema -> Maybe Text
+schemaDiagnostic mode schema = case mode of
+  DescribeConfigText -> Just (renderSchemaText schema)
+  DescribeConfigJson -> Just (renderSchemaJson schema <> "\n")
+  _ -> Nothing
 
+-- | Render a diagnostic available after resolution.
+resolutionDiagnostic :: DiagnosticMode -> ResolveResult a -> Maybe Text
+resolutionDiagnostic mode result = case mode of
+  ExplainText -> Just (renderResolutionText (result ^. #report))
+  ExplainJson -> Just (renderResolutionJson (result ^. #report) <> "\n")
+  CheckConfig -> Just "configuration valid\n"
+  _ -> Nothing
+
 -- | Parse the reusable Configuration and Diagnostics option groups.
 setteiOptions :: Parser SetteiOptions
 setteiOptions = assemble <$> configurationOptions <*> diagnosticOptions
@@ -150,7 +168,7 @@
       SetteiOptions
         { configPaths = configuration ^. #configPaths,
           overrides = configuration ^. #overrides,
-          explainMode = diagnostics ^. #explainMode
+          diagnosticMode = diagnostics ^. #diagnosticMode
         }
 
 configurationOptions :: Parser ConfigurationOptions
@@ -163,7 +181,7 @@
 diagnosticOptions =
   Options.parserOptionGroup
     "Diagnostics"
-    (DiagnosticOptions <$> explainModeOptions)
+    (DiagnosticOptions <$> diagnosticModeOptions)
 
 overrideReader :: Options.ReadM CliOverride
 overrideReader = Options.eitherReader $ \input ->
diff --git a/test/Settei/OptparseTest.hs b/test/Settei/OptparseTest.hs
--- a/test/Settei/OptparseTest.hs
+++ b/test/Settei/OptparseTest.hs
@@ -19,14 +19,13 @@
     [ testCase "final repeated --set wins with shadow trace" $ do
         overrides <- expectParse overrideOptions ["--set", "service.port=9000", "--set", "service.port=9001"]
         environment <- environmentPort 8000
-        result <-
-          expectResolution
-            ( resolve
+        let result =
+              resolve
                 defaultResolveOptions
                 ([portSource "built-in" BuiltInSource 6000, portSource "file" (FileSource "memory") 7000, environment] <> cliSources "arguments" overrides)
                 (required portSetting)
-            )
-        result ^. #value @?= 9001
+        value <- expectResolution result
+        value @?= 9001
         case result ^. #report . #nodes . at servicePort of
           Just node -> do
             node ^. #origin . _Just . #annotations . at "command-line.occurrence" @?= Just "2"
@@ -39,7 +38,7 @@
       testCase "malformed final CLI value does not fall back" $ do
         overrides <- expectParse overrideOptions ["--set", "service.port=9000", "--set", "service.port=broken"]
         environment <- environmentPort 8000
-        case resolve defaultResolveOptions (environment : cliSources "arguments" overrides) (required portSetting) of
+        case (resolve defaultResolveOptions (environment : cliSources "arguments" overrides) (required portSetting)) ^. #answer of
           Left errors -> case NonEmpty.toList errors of
             [DecodeError problem] -> do
               problem ^. #origin . #name @?= "arguments --set #2"
@@ -48,7 +47,8 @@
           Right _ -> fail "expected the malformed final override to fail",
       testCase "secret override spelling omits the value" $ do
         overrides <- expectParse overrideOptions ["--set", "database.password=" <> Text.unpack secretSentinel]
-        result <- expectResolution (resolve defaultResolveOptions (cliSources "arguments" overrides) (required passwordSetting))
+        let result = resolve defaultResolveOptions (cliSources "arguments" overrides) (required passwordSetting)
+        _ <- expectResolution result
         let textOutput = renderResolutionText (result ^. #report)
             jsonOutput = renderResolutionJson (result ^. #report)
         assertBool "secret reached text output" (not (secretSentinel `Text.isInfixOf` textOutput))
@@ -64,8 +64,9 @@
         case input of
           Nothing -> fail "expected the named option source"
           Just sourceValue -> do
-            result <- expectResolution (resolve defaultResolveOptions [sourceValue] (required portSetting))
-            result ^. #value @?= 8080,
+            let result = resolve defaultResolveOptions [sourceValue] (required portSetting)
+            value <- expectResolution result
+            value @?= 8080,
       testCase "setteiOptions parses grouped paths, overrides, and diagnostics" $ do
         parsed <-
           expectParse
@@ -80,10 +81,19 @@
             ]
         parsed ^. #configPaths @?= ["base.yaml", "local.yaml"]
         length (parsed ^. #overrides) @?= 1
-        parsed ^. #explainMode @?= ExplainJson,
-      testCase "explanation modes are mutually exclusive" $ do
-        let parsed = parse explainModeOptions ["--explain-config", "--explain-config-json"]
-        assertBool "both explanation modes unexpectedly parsed" (Options.getParseResult parsed == Nothing),
+        parsed ^. #diagnosticMode @?= ExplainJson,
+      testCase "diagnostic modes default and are mutually exclusive" $ do
+        defaulted <- expectParse diagnosticModeOptions []
+        defaulted @?= NoDiagnostic
+        let parsed = parse diagnosticModeOptions ["--explain-config", "--explain-config-json"]
+        assertBool "both diagnostic modes unexpectedly parsed" (Options.getParseResult parsed == Nothing),
+      testCase "diagnostic helpers render the requested outputs" $ do
+        let schema = describe (required portSetting)
+            result = resolve defaultResolveOptions [portSource "test" BuiltInSource 8080] (required portSetting)
+        assertBool "text schema diagnostic missing" (schemaDiagnostic DescribeConfigText schema /= Nothing)
+        assertBool "JSON schema diagnostic missing" (schemaDiagnostic DescribeConfigJson schema /= Nothing)
+        resolutionDiagnostic CheckConfig result @?= Just "configuration valid\n"
+        resolutionDiagnostic NoDiagnostic result @?= Nothing,
       testCase "help groups configuration and diagnostics by intent" $ do
         let parserInfo = Options.info (setteiOptions Options.<**> Options.helper) Options.fullDesc
         case Options.execParserPure Options.defaultPrefs parserInfo ["--help"] of
@@ -104,19 +114,23 @@
     Nothing -> fail "expected command-line parsing to succeed"
     Just value -> pure value
 
-expectResolution :: Either (NonEmpty ConfigError) a -> IO a
-expectResolution = \case
+expectResolution :: ResolveResult a -> IO a
+expectResolution result = case result ^. #answer of
   Left _ -> fail "expected configuration resolution to succeed"
   Right value -> pure value
 
 environmentPort :: Int -> IO Source
-environmentPort value =
-  case envSource
-    "environment"
-    [binding (EnvName "SERVICE_PORT") servicePort]
-    (envSnapshot [("SERVICE_PORT", Text.pack (show value))]) of
-    Left _ -> fail "expected a valid environment source"
-    Right sourceValue -> pure sourceValue
+environmentPort value = do
+  validated <-
+    either
+      (fail . Text.unpack . renderEnvErrorsText)
+      pure
+      (bindings [binding (EnvName "SERVICE_PORT") servicePort])
+  pure
+    ( environmentSource
+        validated
+        (envSnapshot [("SERVICE_PORT", Text.pack (show value))])
+    )
 
 portSource :: Text -> SourceKind -> Int -> Source
 portSource name kind value =
