diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# CHANGELOG
+
+## Version 0.1.0 (2025-07-01)
+
+Released upon an unsuspecting world
diff --git a/github-actions.cabal b/github-actions.cabal
--- a/github-actions.cabal
+++ b/github-actions.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               github-actions
-version:            0.1.0.0
+version:            0.1.1.0
 synopsis:           Github Actions
 description:
   This library provides types and instances for serializing and deserializing
@@ -15,8 +15,11 @@
 copyright:          Copyright (C) 2025 Bellroy Pty Ltd
 category:           Language
 build-type:         Simple
-extra-source-files:
+extra-doc-files:
+  CHANGELOG.md
   README.md
+
+extra-source-files:
   test/golden/configuration-main.golden.yml
   test/golden/configuration-main.hs.txt
   test/golden/configuration-main.yml
@@ -36,11 +39,11 @@
   build-depends:
     , aeson               ^>=2.2.3.0
     , base                >=4.14      && <4.22
-    , containers          ==0.6.7     || ==0.6.8 || ==0.7   || ==0.8
+    , containers          ^>=0.6.7    || ^>=0.7   || ^>=0.8
     , hedgehog            ^>=1.5
     , hoist-error         ^>=0.3
     , string-interpolate  ^>=0.3.3
-    , text                ==1.2.4.1   || ==2.0.2 || ==2.1.1
+    , text                ^>=1.2.4.1  || ^>=2.0.2 || ^>=2.1.1
     , vector              ^>=0.13.0.0
 
 library
@@ -56,14 +59,17 @@
     Language.Github.Actions.Job.Container
     Language.Github.Actions.Job.Environment
     Language.Github.Actions.Job.Id
+    Language.Github.Actions.Job.Needs
     Language.Github.Actions.Job.Strategy
     Language.Github.Actions.Permissions
+    Language.Github.Actions.RunIf
     Language.Github.Actions.Service
     Language.Github.Actions.Service.Id
     Language.Github.Actions.Shell
     Language.Github.Actions.Step
     Language.Github.Actions.Step.Id
     Language.Github.Actions.Step.With
+    Language.Github.Actions.UnstructuredMap
     Language.Github.Actions.Workflow
     Language.Github.Actions.Workflow.Trigger
 
diff --git a/src/Language/Github/Actions/Job.hs b/src/Language/Github/Actions/Job.hs
--- a/src/Language/Github/Actions/Job.hs
+++ b/src/Language/Github/Actions/Job.hs
@@ -43,12 +43,14 @@
 import qualified Language.Github.Actions.Job.Container as JobContainer
 import Language.Github.Actions.Job.Environment (JobEnvironment)
 import qualified Language.Github.Actions.Job.Environment as JobEnvironment
-import Language.Github.Actions.Job.Id (JobId)
-import qualified Language.Github.Actions.Job.Id as JobId
+import Language.Github.Actions.Job.Needs (JobNeeds)
+import qualified Language.Github.Actions.Job.Needs as JobNeeds
 import Language.Github.Actions.Job.Strategy (JobStrategy)
 import qualified Language.Github.Actions.Job.Strategy as JobStrategy
 import Language.Github.Actions.Permissions (Permissions)
 import qualified Language.Github.Actions.Permissions as Permissions
+import Language.Github.Actions.RunIf (RunIf)
+import qualified Language.Github.Actions.RunIf as RunIf
 import Language.Github.Actions.Service (Service)
 import qualified Language.Github.Actions.Service as Service
 import Language.Github.Actions.Service.Id (ServiceId)
@@ -93,13 +95,13 @@
     -- | Display name for the job
     jobName :: Maybe Text,
     -- | Jobs this job depends on
-    needs :: Maybe (NonEmpty JobId),
+    needs :: Maybe JobNeeds,
     -- | Outputs from this job
     outputs :: Map Text Text,
     -- | Permissions for this job
     permissions :: Maybe Permissions,
     -- | Condition for running this job
-    runIf :: Maybe Text,
+    runIf :: Maybe RunIf,
     -- | Runner type (e.g., "ubuntu-latest")
     runsOn :: Maybe Text,
     -- | Secrets available to this job
@@ -179,10 +181,10 @@
   env <- genTextMap
   environment <- Gen.maybe JobEnvironment.gen
   jobName <- Gen.maybe genText
-  needs <- Gen.maybe (Gen.nonEmpty (Range.linear 1 5) JobId.gen)
+  needs <- Gen.maybe JobNeeds.gen
   outputs <- genTextMap
   permissions <- Gen.maybe Permissions.gen
-  runIf <- Gen.maybe genText
+  runIf <- Gen.maybe RunIf.gen
   runsOn <- Gen.maybe genText
   secrets <- genTextMap
   services <- Gen.map (Range.linear 1 5) $ liftA2 (,) ServiceId.gen Service.gen
diff --git a/src/Language/Github/Actions/Job/Needs.hs b/src/Language/Github/Actions/Job/Needs.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Github/Actions/Job/Needs.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- |
+-- Module      : Language.Github.Actions.Job.Needs
+-- Description : Job dependency specification for GitHub Actions
+-- Copyright   : (c) 2025 Bellroy Pty Ltd
+-- License     : BSD-3-Clause
+-- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>
+--
+-- This module provides the 'JobNeeds' type for representing job dependencies
+-- in GitHub Actions workflows. GitHub Actions allows both strings and
+-- lists of strings for the 'needs' field.
+--
+-- Examples of valid 'needs' specifications:
+-- * @needs: build@ - Single job specified as a string
+-- * @needs: [build]@ - Single job specified as a list of strings
+-- * @needs: [build, test]@ - Multiple job dependencies specified as a list of strings
+--
+-- For more information about GitHub Actions job dependencies, see:
+-- <https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds>
+module Language.Github.Actions.Job.Needs
+  ( JobNeeds (..),
+    gen,
+  )
+where
+
+import Data.Aeson (FromJSON, ToJSON (..), Value (..))
+import qualified Data.Aeson as Aeson
+import Data.List.NonEmpty (NonEmpty)
+import GHC.Generics (Generic)
+import Hedgehog (MonadGen)
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import Language.Github.Actions.Job.Id (JobId)
+import qualified Language.Github.Actions.Job.Id as JobId
+
+-- | Job dependency specification that preserves YAML representation.
+--
+-- GitHub Actions supports flexible job dependency specification:
+--
+-- * 'JobNeedsString' - Single job dependency as string like @needs: build@
+-- * 'JobNeedsArray' - Multiple job dependencies as array like @needs: [build, test]@
+--
+-- Examples:
+--
+-- @
+-- -- Single job dependency (string form)
+-- stringDep :: JobNeeds
+-- stringDep = JobNeedsString (JobId "build")
+--
+-- -- Multiple job dependencies (array form)
+-- arrayDeps :: JobNeeds
+-- arrayDeps = JobNeedsArray (JobId "build" :| [JobId "test", JobId "lint"])
+-- @
+--
+-- The type preserves the original YAML format during round-trip serialization.
+-- A string input will serialize back to a string, and an array input will
+-- serialize back to an array, preventing information loss.
+data JobNeeds
+  = JobNeedsString JobId
+  | JobNeedsArray (NonEmpty JobId)
+  deriving stock (Eq, Generic, Ord, Show)
+
+instance FromJSON JobNeeds where
+  parseJSON v@(Array _) = JobNeedsArray <$> Aeson.parseJSON v
+  parseJSON v = JobNeedsString <$> Aeson.parseJSON v
+
+instance ToJSON JobNeeds where
+  toJSON (JobNeedsString jobId) = toJSON jobId
+  toJSON (JobNeedsArray jobIds) = toJSON jobIds
+
+gen :: (MonadGen m) => m JobNeeds
+gen =
+  Gen.choice
+    [ JobNeedsString <$> JobId.gen,
+      JobNeedsArray <$> Gen.nonEmpty (Range.linear 1 5) JobId.gen
+    ]
diff --git a/src/Language/Github/Actions/RunIf.hs b/src/Language/Github/Actions/RunIf.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Github/Actions/RunIf.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module      : Language.Github.Actions.RunIf
+-- Description : Flexible conditional expressions for GitHub Actions
+-- Copyright   : (c) 2025 Bellroy Pty Ltd
+-- License     : BSD-3-Clause
+-- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>
+--
+-- This module provides the 'RunIf' type for representing conditional expressions
+-- in GitHub Actions workflows. GitHub Actions allows both boolean and string
+-- expressions in 'if' conditions for jobs and steps.
+--
+-- Examples of valid 'if' conditions:
+-- * @if: false@ - Boolean
+-- * @if: "github.ref == 'refs/heads/main'"@ - String
+--
+-- For more information about GitHub Actions conditional expressions, see:
+-- <https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif>
+module Language.Github.Actions.RunIf
+  ( RunIf (..),
+    gen,
+  )
+where
+
+import Data.Aeson (FromJSON, ToJSON (..), Value (..))
+import qualified Data.Aeson as Aeson
+import Data.Text (Text)
+import GHC.Generics (Generic)
+import Hedgehog (MonadGen)
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+
+-- | A conditional expression that can be either a boolean or string.
+--
+-- GitHub Actions supports flexible 'if' conditions:
+--
+-- * 'RunIfBool' - Simple boolean values like @true@ or @false@
+-- * 'RunIfString' - GitHub expressions like @"github.ref == 'refs/heads/main'"@
+--
+-- Examples:
+--
+-- @
+-- -- Simple boolean condition
+-- simpleFalse :: RunIf
+-- simpleFalse = RunIfBool False
+--
+-- -- GitHub expression condition
+-- branchCheck :: RunIf
+-- branchCheck = RunIfString "github.ref == 'refs/heads/main'"
+-- @
+--
+-- The type preserves the original format during round-trip serialization,
+-- so a boolean input remains a boolean in the output YAML.
+data RunIf
+  = RunIfBool Bool
+  | RunIfString Text
+  deriving stock (Eq, Generic, Ord, Show)
+
+instance FromJSON RunIf where
+  parseJSON (Bool b) = pure $ RunIfBool b
+  parseJSON (String s) = pure $ RunIfString s
+  parseJSON v = fail $ "Expected Bool or String for RunIf, got: " ++ show v
+
+instance ToJSON RunIf where
+  toJSON (RunIfBool b) = Bool b
+  toJSON (RunIfString s) = String s
+
+gen :: (MonadGen m) => m RunIf
+gen =
+  Gen.choice
+    [ RunIfBool <$> Gen.bool,
+      RunIfString <$> Gen.text (Range.linear 5 50) Gen.alphaNum
+    ]
diff --git a/src/Language/Github/Actions/Step.hs b/src/Language/Github/Actions/Step.hs
--- a/src/Language/Github/Actions/Step.hs
+++ b/src/Language/Github/Actions/Step.hs
@@ -35,6 +35,8 @@
 import Hedgehog (MonadGen)
 import qualified Hedgehog.Gen as Gen
 import qualified Hedgehog.Range as Range
+import Language.Github.Actions.RunIf (RunIf)
+import qualified Language.Github.Actions.RunIf as RunIf
 import Language.Github.Actions.Shell (Shell)
 import qualified Language.Github.Actions.Shell as Shell
 import Language.Github.Actions.Step.Id (StepId)
@@ -82,7 +84,7 @@
     -- | Command or script to run
     run :: Maybe Text,
     -- | Condition for running this step
-    runIf :: Maybe Text,
+    runIf :: Maybe RunIf,
     -- | Shell to use for running commands
     shell :: Maybe Shell,
     -- | Unique identifier for this step
@@ -139,7 +141,7 @@
   env <- genTextMap
   name <- Gen.maybe genText
   run <- Gen.maybe genText
-  runIf <- Gen.maybe genText
+  runIf <- Gen.maybe RunIf.gen
   shell <- Gen.maybe Shell.gen
   stepId <- Gen.maybe StepId.gen
   timeoutMinutes <- Gen.maybe $ Gen.int (Range.linear 1 120)
diff --git a/src/Language/Github/Actions/Step/With.hs b/src/Language/Github/Actions/Step/With.hs
--- a/src/Language/Github/Actions/Step/With.hs
+++ b/src/Language/Github/Actions/Step/With.hs
@@ -30,7 +30,6 @@
 import Data.Aeson (FromJSON, ToJSON (..), (.:), (.:?), (.=))
 import qualified Data.Aeson as Aeson
 import qualified Data.Aeson.KeyMap as AesonKeyMap
-import Data.Map (Map)
 import Data.Maybe (catMaybes)
 import qualified Data.Set as Set
 import Data.Text (Text)
@@ -38,6 +37,8 @@
 import Hedgehog (MonadGen)
 import qualified Hedgehog.Gen as Gen
 import qualified Hedgehog.Range as Range
+import Language.Github.Actions.UnstructuredMap (UnstructuredMap)
+import qualified Language.Github.Actions.UnstructuredMap as UnstructuredMap
 
 -- | Docker container arguments for Docker actions.
 --
@@ -95,14 +96,14 @@
   = -- | Docker action arguments
     StepWithDockerArgs StepWithDockerArgsAttrs
   | -- | Environment variables/general inputs
-    StepWithEnv (Map Text Text)
+    StepWithEnv UnstructuredMap
   deriving stock (Eq, Generic, Ord, Show)
 
 instance FromJSON StepWith where
   parseJSON = Aeson.withObject "StepWith" $ \o ->
     let objectKeySet = Set.fromList (AesonKeyMap.keys o)
         dockerKeySet = Set.fromList ["entryPoint", "args"]
-     in if objectKeySet `Set.isSubsetOf` dockerKeySet
+     in if not (null objectKeySet) && objectKeySet `Set.isSubsetOf` dockerKeySet
           then do
             entryPoint <- o .: "entryPoint"
             args <- o .:? "args"
@@ -127,8 +128,7 @@
         entryPoint <- genText
         args <- Gen.maybe genText
         pure StepWithDockerArgsAttrs {..},
-      StepWithEnv <$> genTextMap
+      StepWithEnv <$> UnstructuredMap.gen
     ]
   where
     genText = Gen.text (Range.linear 1 5) Gen.alphaNum
-    genTextMap = Gen.map (Range.linear 1 5) $ liftA2 (,) genText genText
diff --git a/src/Language/Github/Actions/UnstructuredMap.hs b/src/Language/Github/Actions/UnstructuredMap.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Github/Actions/UnstructuredMap.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module      : Language.Github.Actions.UnstructuredMap
+-- Description : Flexible value types for GitHub Actions YAML parsing
+-- Copyright   : (c) 2025 Bellroy Pty Ltd
+-- License     : BSD-3-Clause
+-- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>
+--
+-- This module provides the 'UnstructuredMap' type for representing a map of
+-- values that can be strings, numbers, or booleans in GitHub Actions YAML files.
+--
+-- GitHub Actions allows flexible typing in many contexts:
+-- * @retention-days: 1@ (number)
+-- * @retention-days: "1"@ (string)
+-- * @should-retain: false@ (boolean)
+--
+-- This type preserves the original YAML type during round-trip parsing,
+-- ensuring that numeric values remain numeric and strings remain strings.
+module Language.Github.Actions.UnstructuredMap
+  ( UnstructuredValue (..),
+    UnstructuredMap (..),
+    renderUnstructuredValue,
+    gen,
+  )
+where
+
+import Data.Aeson (FromJSON, ToJSON (..), Value (..))
+import qualified Data.Aeson as Aeson
+import Data.Map (Map)
+import Data.Text (Text)
+import qualified Data.Text as Text
+import GHC.Generics (Generic)
+import Hedgehog (MonadGen)
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+
+-- | A map that can have values of string, number, or boolean.
+--
+-- This type is designed to handle the flexible typing that GitHub Actions
+-- allows in YAML files.
+--
+-- The type preserves the original format during round-trip serialization,
+-- so numeric inputs remain numeric in the output YAML.
+data UnstructuredValue
+  = UnstructuredValueString Text
+  | UnstructuredValueNumber Double
+  | UnstructuredValueBool Bool
+  deriving stock (Eq, Generic, Ord, Show)
+
+instance FromJSON UnstructuredValue where
+  parseJSON (String s) = pure $ UnstructuredValueString s
+  parseJSON (Number n) = pure $ UnstructuredValueNumber (realToFrac n)
+  parseJSON (Bool b) = pure $ UnstructuredValueBool b
+  parseJSON v = fail $ "Expected String, Number, or Bool for UnstructuredValue, got: " ++ show v
+
+instance ToJSON UnstructuredValue where
+  toJSON (UnstructuredValueString s) = String s
+  toJSON (UnstructuredValueNumber n) = Number (fromRational (toRational n))
+  toJSON (UnstructuredValueBool b) = Bool b
+
+renderUnstructuredValue :: UnstructuredValue -> Text
+renderUnstructuredValue (UnstructuredValueString s) = s
+renderUnstructuredValue (UnstructuredValueNumber n) =
+  -- Format numbers nicely, avoiding unnecessary decimal places for integers
+  if n == fromInteger (round n)
+    then Text.pack (show (round n :: Integer))
+    else Text.pack (show n)
+renderUnstructuredValue (UnstructuredValueBool b) = if b then "true" else "false"
+
+newtype UnstructuredMap = UnstructuredMap (Map Text UnstructuredValue)
+  deriving stock (Eq, Generic, Ord, Show)
+  deriving newtype (FromJSON, ToJSON)
+
+genUnstructuredValue :: (MonadGen m) => m UnstructuredValue
+genUnstructuredValue =
+  Gen.choice
+    [ UnstructuredValueString <$> Gen.text (Range.linear 1 20) Gen.alphaNum,
+      UnstructuredValueNumber <$> Gen.realFloat (Range.linearFrac 0 1000),
+      UnstructuredValueBool <$> Gen.bool
+    ]
+
+gen :: (MonadGen m) => m UnstructuredMap
+gen = UnstructuredMap <$> Gen.map (Range.linear 0 10) genKeyValue
+  where
+    genKeyValue = do
+      key <- Gen.text (Range.linear 1 20) Gen.alphaNum
+      value <- genUnstructuredValue
+      pure (key, value)
diff --git a/src/Language/Github/Actions/Workflow/Trigger.hs b/src/Language/Github/Actions/Workflow/Trigger.hs
--- a/src/Language/Github/Actions/Workflow/Trigger.hs
+++ b/src/Language/Github/Actions/Workflow/Trigger.hs
@@ -75,6 +75,8 @@
     ReleaseActivityType (..),
     WorkflowCallAttributes (..),
     WorkflowDispatchAttributes (..),
+    WorkflowDispatchInput (..),
+    WorkflowDispatchInputType (..),
     WorkflowRunActivityType (..),
     WorkflowRunTriggerAttributes (..),
     WorkflowTrigger (..),
diff --git a/test/Language/Github/Actions/WorkflowTest.hs b/test/Language/Github/Actions/WorkflowTest.hs
--- a/test/Language/Github/Actions/WorkflowTest.hs
+++ b/test/Language/Github/Actions/WorkflowTest.hs
@@ -50,7 +50,7 @@
               putStrLn $ "roundtrip " <> takeBaseName testYamlFilePath
               eitherWorkflow <- Yaml.decodeFileEither @Workflow testYamlFilePath
               either
-                (BS.writeFile outputFilePath >> (\_ -> fail "YAML decoding failed"))
+                (BS.writeFile outputFilePath >> (\e -> fail $ "YAML decoding failed: " ++ show e))
                 (\workflow -> writeOutputFiles outputFilePath haskellOutputFilePath workflow >> pure workflow)
                 $ first (encodeUtf8 . Text.pack . Yaml.prettyPrintParseException) eitherWorkflow
     writeOutputFiles :: FilePath -> FilePath -> Workflow -> IO ()
diff --git a/test/golden/configuration-main.hs.txt b/test/golden/configuration-main.hs.txt
--- a/test/golden/configuration-main.hs.txt
+++ b/test/golden/configuration-main.hs.txt
@@ -18,7 +18,7 @@
               , env = fromList []
               , environment = Nothing
               , jobName = Nothing
-              , needs = Just (JobId "validateContent" :| [])
+              , needs = Just (JobNeedsArray (JobId "validateContent" :| []))
               , outputs = fromList []
               , permissions = Nothing
               , runIf = Nothing
@@ -34,7 +34,8 @@
                        , run = Nothing
                        , runIf =
                            Just
-                             "github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable'"
+                             (RunIfString
+                                "github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable'")
                        , shell = Nothing
                        , stepId = Nothing
                        , timeoutMinutes = Nothing
@@ -42,10 +43,11 @@
                        , with =
                            Just
                              (StepWithEnv
-                                (fromList
-                                   [ ( "fetch-depth" , "0" )
-                                   , ( "token" , "${{ env.GH_TOKEN }}" )
-                                   ]))
+                                (UnstructuredMap
+                                   (fromList
+                                      [ ( "fetch-depth" , UnstructuredValueString "0" )
+                                      , ( "token" , UnstructuredValueString "${{ env.GH_TOKEN }}" )
+                                      ])))
                        , workingDirectory = Nothing
                        } :|
                        [ Step
@@ -55,7 +57,8 @@
                            , run = Nothing
                            , runIf =
                                Just
-                                 "github.ref != 'refs/heads/master' && github.ref != 'refs/heads/stable'"
+                                 (RunIfString
+                                    "github.ref != 'refs/heads/master' && github.ref != 'refs/heads/stable'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -63,10 +66,13 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "fetch-depth" , "1" )
-                                       , ( "token" , "${{ env.GH_TOKEN }}" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "fetch-depth" , UnstructuredValueString "1" )
+                                          , ( "token"
+                                            , UnstructuredValueString "${{ env.GH_TOKEN }}"
+                                            )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -91,7 +97,7 @@
                            , run =
                                Just
                                  "echo \"DESTINATION=Staging Environment\" >> $GITHUB_ENV\necho \"ENVIRONMENT=staging\" >> $GITHUB_ENV\n"
-                           , runIf = Just "github.ref == 'refs/heads/master'"
+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -106,7 +112,7 @@
                            , run =
                                Just
                                  "echo \"DESTINATION=Production Environment\" >> $GITHUB_ENV\necho \"ENVIRONMENT=production\" >> $GITHUB_ENV\n"
-                           , runIf = Just "github.ref == 'refs/heads/stable'"
+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/stable'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -127,12 +133,16 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "bellroy-nix-cache-access" , "none" )
-                                       , ( "nix-config-access-tokens"
-                                         , "github.com=${{ secrets.READ_HASKELL_REPO_PAT }}"
-                                         )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "bellroy-nix-cache-access"
+                                            , UnstructuredValueString "none"
+                                            )
+                                          , ( "nix-config-access-tokens"
+                                            , UnstructuredValueString
+                                                "github.com=${{ secrets.READ_HASKELL_REPO_PAT }}"
+                                            )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -179,7 +189,13 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList [ ( "path" , "${{ env.GIT_REPOSITORY_PATH }}" ) ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "path"
+                                            , UnstructuredValueString
+                                                "${{ env.GIT_REPOSITORY_PATH }}"
+                                            )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -206,7 +222,8 @@
                            , run =
                                Just
                                  "set +e\nIFS= output=$(./scripts/check_configuration_csvs_sorted 2>&1)\nstatus=$?\n\nif (($status)); then\n  echo \"Configuration CSV files are not sorted:\" >>/tmp/pr-comment.txt\n  echo \"\" >>/tmp/pr-comment.txt\n  echo \"\\`\\`\\`\" >>/tmp/pr-comment.txt\n  echo $output >>/tmp/pr-comment.txt\n  echo \"\\`\\`\\`\" >>/tmp/pr-comment.txt\n  exit $status\nelse\n  echo \"All configuration CSV files are sorted.\"\nfi\n"
-                           , runIf = Just "steps.check_changes.outputs.csv_changed == '1'"
+                           , runIf =
+                               Just (RunIfString "steps.check_changes.outputs.csv_changed == '1'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -227,11 +244,15 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )
-                                       , ( "tool-name" , "byproduct" )
-                                       , ( "tool-version" , "latest" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "token"
+                                            , UnstructuredValueString
+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"
+                                            )
+                                          , ( "tool-name" , UnstructuredValueString "byproduct" )
+                                          , ( "tool-version" , UnstructuredValueString "latest" )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -247,11 +268,15 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )
-                                       , ( "tool-name" , "gitapult" )
-                                       , ( "tool-version" , "latest" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "token"
+                                            , UnstructuredValueString
+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"
+                                            )
+                                          , ( "tool-name" , UnstructuredValueString "gitapult" )
+                                          , ( "tool-version" , UnstructuredValueString "latest" )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -267,11 +292,17 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )
-                                       , ( "tool-name" , "config-rules-cli" )
-                                       , ( "tool-version" , "latest" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "token"
+                                            , UnstructuredValueString
+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"
+                                            )
+                                          , ( "tool-name"
+                                            , UnstructuredValueString "config-rules-cli"
+                                            )
+                                          , ( "tool-version" , UnstructuredValueString "latest" )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -287,11 +318,15 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )
-                                       , ( "tool-name" , "powerful-owl" )
-                                       , ( "tool-version" , "latest" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "token"
+                                            , UnstructuredValueString
+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"
+                                            )
+                                          , ( "tool-name" , UnstructuredValueString "powerful-owl" )
+                                          , ( "tool-version" , UnstructuredValueString "latest" )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -376,7 +411,7 @@
                            , run =
                                Just
                                  "set +e\nIFS= output=$(./gitapult changeset --git_root \"$GITHUB_WORKSPACE\" --from \"${{ env.MERGE_BASE }}\" | ./byproduct validate --markdown 2>&1)\nstatus=$?\n\nif (($status)); then\n  echo $output >>/tmp/pr-comment.txt\n  exit $status\nelse\n  echo \"Byproduct validation succeeded.\"\nfi\n"
-                           , runIf = Just "env.PR_NUMBER != 0"
+                           , runIf = Just (RunIfString "env.PR_NUMBER != 0")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -468,7 +503,8 @@
                                  "if [ $(git status shipping/tests --porcelain=v1 | wc -l) -gt 0 ]\nthen\n  git add shipping/tests\n  git commit -m \"Update shipping tests\"\n  echo \"GENERATED_ANOTHER_COMMIT=true\" >> $GITHUB_ENV\nfi\n"
                            , runIf =
                                Just
-                                 "env.PR_NUMBER != 0 && github.ref != 'refs/heads/stable' && github.ref != 'refs/heads/master'"
+                                 (RunIfString
+                                    "env.PR_NUMBER != 0 && github.ref != 'refs/heads/stable' && github.ref != 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -515,7 +551,8 @@
                                  "if [ $(git status promotions/tests --porcelain=v1 | wc -l) -gt 0 ]\nthen\n  git add promotions/tests\n  git commit -m \"Update promotion tests\"\n  echo \"GENERATED_ANOTHER_COMMIT=true\" >> $GITHUB_ENV\nfi\n"
                            , runIf =
                                Just
-                                 "env.PR_NUMBER != 0 && github.ref != 'refs/heads/stable' && github.ref != 'refs/heads/master'"
+                                 (RunIfString
+                                    "env.PR_NUMBER != 0 && github.ref != 'refs/heads/stable' && github.ref != 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -531,7 +568,8 @@
                            , run =
                                Just
                                  "git push --set-upstream origin HEAD\n\ngh run cancel ${{ github.run_id }}\ngh run watch ${{ github.run_id }}\n"
-                           , runIf = Just "env.GENERATED_ANOTHER_COMMIT == 'true'"
+                           , runIf =
+                               Just (RunIfString "env.GENERATED_ANOTHER_COMMIT == 'true'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -563,7 +601,8 @@
                                  "echo \"COMMIT_CHANGES<<EOF\" >> $GITHUB_ENV\necho \"$(git log --oneline --format=\\\"%s\\\" $(git tag -l [0-9]*_${{ env.ENVIRONMENT }} | tail -n 1)..${{ steps.extract_branch.outputs.branch }} | grep -v 'Merge pull request' | grep -v \"Merge branch 'master' into\" | sed -e 's/^\"//' -e 's/\"$//' | cat)\" >> $GITHUB_ENV\necho \"EOF\" >> $GITHUB_ENV\n"
                            , runIf =
                                Just
-                                 "github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master'"
+                                 (RunIfString
+                                    "github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master'")
                            , shell = Just (Bash Nothing)
                            , stepId = Just (StepId "extract_changes")
                            , timeoutMinutes = Nothing
@@ -580,7 +619,8 @@
                            , run = Nothing
                            , runIf =
                                Just
-                                 "github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master'"
+                                 (RunIfString
+                                    "github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Just (StepId "slack-deploy")
                            , timeoutMinutes = Nothing
@@ -590,12 +630,16 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "channel-id" , "${{ env.SLACK_CHANNEL_ID }}" )
-                                       , ( "payload"
-                                         , "{\n  \"text\": \":loudspeaker: *${{ github.actor }}* is deploying *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}* from *${{ steps.extract_branch.outputs.branch }}*\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"ffd966\",\n      \"fields\": [\n        {\n          \"title\": \"What is being deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"In Progress\"\n        }\n      ]\n    }\n  ]\n}\n"
-                                         )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "channel-id"
+                                            , UnstructuredValueString "${{ env.SLACK_CHANNEL_ID }}"
+                                            )
+                                          , ( "payload"
+                                            , UnstructuredValueString
+                                                "{\n  \"text\": \":loudspeaker: *${{ github.actor }}* is deploying *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}* from *${{ steps.extract_branch.outputs.branch }}*\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"ffd966\",\n      \"fields\": [\n        {\n          \"title\": \"What is being deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"In Progress\"\n        }\n      ]\n    }\n  ]\n}\n"
+                                            )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -605,7 +649,7 @@
                            , run =
                                Just
                                  "set -e\n# XXX: Gitapult cannot distinguish datasets, so we push all data to\n# both endpoints and allow the server to decide what to do.\n# Kafka Webhooks accepts all datasets at once\n./gitapult run \\\n  --git_root \"$GITHUB_WORKSPACE\" \\\n  --authorization_token \"$KW_STAGING_API_KEY\" \\\n  --changeset_url \"$KW_STAGING_BASE_URL/gitapult/configuration/records\" \\\n  --revision_url \"$KW_STAGING_BASE_URL/gitapult/configuration/revision\" \\\n  --api_key \\\n  --compress \\\n  --max-payload-bytes 3000000\n\n# XXX: V3 doesn't care about rendering data, but it is too large to send.\n# As at 2024-02-14 gitapult does not support selecting which data to send.\ngit -C \"$GIT_REPOSITORY_PATH\" rm feeds/rendering_data/.gitapult.json\n./gitapult run \\\n  --git_root \"$GITHUB_WORKSPACE\" \\\n  --authorization_token \"$SF_STAGING_API_KEY\" \\\n  --changeset_url \"$SF_STAGING_BASE_URL/api/v1/configuration/shipping\" \\\n  --revision_url \"$SF_STAGING_BASE_URL/api/v1/configuration/shipping/revisions\" && \\\ngit -C \"$GIT_REPOSITORY_PATH\" restore --staged feeds/rendering_data/.gitapult.json\ngit -C \"$GIT_REPOSITORY_PATH\" restore feeds/rendering_data/.gitapult.json\n"
-                           , runIf = Just "github.ref == 'refs/heads/master'"
+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -620,7 +664,8 @@
                            , run =
                                Just
                                  "git tag \"${{ join(steps.current-time.outputs.*, '\\n') }}_staging\" && git push --tags\n"
-                           , runIf = Just "success() && github.ref == 'refs/heads/master'"
+                           , runIf =
+                               Just (RunIfString "success() && github.ref == 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -635,7 +680,7 @@
                            , run =
                                Just
                                  "curl -X POST -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls -d '{\"head\":\"master\",\"base\":\"stable\",\"title\":\"Merge branch master into stable\",\"body\":\"This PR was automatically generated by CI.\"}'\nPR_URL=$(curl -sX GET -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls\\?head\\=bellroy:master\\&base\\=stable | jq -r '.[0].html_url | select(length>0)')\necho \"PR_URL=$PR_URL\" >> $GITHUB_ENV\n"
-                           , runIf = Just "github.ref == 'refs/heads/master'"
+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -650,7 +695,7 @@
                            , run =
                                Just
                                  "PR_RESPONSE=$(curl -sX GET -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls\\?head\\=bellroy:master\\&base\\=stable | jq -r '.[0]')\nPR_NUM=$(echo \"$PR_RESPONSE\" | jq -r \".number\")\nPR_ASSIGNEE=$(echo \"$PR_RESPONSE\" | jq -r \".assignee\")\nCOMMITS_RESPONSE=$(curl -sX GET -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUM/commits)\nFIRST_COMMITTER=$(curl -sX GET -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUM/commits | jq -r '. | sort_by(.commit.author.date) | .[0] | .author.login')\n# special case for squashed PRs created by trike-deploy e.g. gha-workflows\nif [[ \"$FIRST_COMMITTER\" == \"trike-deploy\" ]]; then\n    FIRST_COMMITTER=$(echo \"$COMMITS_RESPONSE\" | jq -r '. | sort_by(.commit.author.date) | .[0]' | sed -En \"s/.*Co-authored-by: (.*) <.*@.*>\\\",/\\1/p\")\nfi\nif [[ \"$PR_ASSIGNEE\" == \"null\" ]]; then\n    curl -sX POST -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUM/assignees -d \"{\\\"assignees\\\":[\\\"$FIRST_COMMITTER\\\"]}\"\nfi\n"
-                           , runIf = Just "github.ref == 'refs/heads/master'"
+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -665,7 +710,7 @@
                            , run =
                                Just
                                  "set -e\n# XXX: Gitapult cannot distinguish datasets, so we push all data to\n# both endpoints and allow the server to decide what to do.\n# Kafka Webhooks accepts all datasets at once\n./gitapult run \\\n  --git_root \"$GITHUB_WORKSPACE\" \\\n  --authorization_token \"$KW_PRODUCTION_API_KEY\" \\\n  --changeset_url \"$KW_PRODUCTION_BASE_URL/gitapult/configuration/records\" \\\n  --revision_url \"$KW_PRODUCTION_BASE_URL/gitapult/configuration/revision\" \\\n  --api_key \\\n  --compress \\\n  --max-payload-bytes 3000000\n\n# XXX: V3 doesn't care about rendering data, but it is too large to send.\n# As at 2024-02-14 gitapult does not support selecting which data to send.\ngit -C \"$GIT_REPOSITORY_PATH\" rm feeds/rendering_data/.gitapult.json\n./gitapult run \\\n  --git_root \"$GITHUB_WORKSPACE\" \\\n  --authorization_token \"$SF_PRODUCTION_API_KEY\" \\\n  --changeset_url \"$SF_PRODUCTION_BASE_URL/api/v1/configuration/shipping\" \\\n  --revision_url \"$SF_PRODUCTION_BASE_URL/api/v1/configuration/shipping/revisions\" && \\\ngit -C \"$GIT_REPOSITORY_PATH\" restore --staged feeds/rendering_data/.gitapult.json\ngit -C \"$GIT_REPOSITORY_PATH\" restore feeds/rendering_data/.gitapult.json\n"
-                           , runIf = Just "github.ref == 'refs/heads/stable'"
+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/stable'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -680,7 +725,8 @@
                            , run =
                                Just
                                  "git tag \"${{ join(steps.current-time.outputs.*, '\\n') }}_production\" && git push --tags\n"
-                           , runIf = Just "success() && github.ref == 'refs/heads/stable'"
+                           , runIf =
+                               Just (RunIfString "success() && github.ref == 'refs/heads/stable'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -695,7 +741,7 @@
                            , run =
                                Just
                                  "echo \"SUCCESS_MESSAGE=:tada: \\nA PR has been opened against the stable branch: ${{ env.PR_URL }}\" >> $GITHUB_ENV\necho \"SUCCESS_MESSAGE_BODY=https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks\\n\\n*PR for Production:* ${{ env.PR_URL }}\" >> $GITHUB_ENV\n"
-                           , runIf = Just "github.ref == 'refs/heads/master'"
+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -710,7 +756,7 @@
                            , run =
                                Just
                                  "echo \"SUCCESS_MESSAGE=:tada:\" >> $GITHUB_ENV\necho \"SUCCESS_MESSAGE_BODY=https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks\" >> $GITHUB_ENV\n"
-                           , runIf = Just "github.ref == 'refs/heads/stable'"
+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/stable'")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -727,7 +773,8 @@
                            , run = Nothing
                            , runIf =
                                Just
-                                 "success() && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master')"
+                                 (RunIfString
+                                    "success() && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master')")
                            , shell = Nothing
                            , stepId = Just (StepId "slack-deploy-success")
                            , timeoutMinutes = Nothing
@@ -737,13 +784,20 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "channel-id" , "${{ env.SLACK_CHANNEL_ID }}" )
-                                       , ( "payload"
-                                         , "{\n  \"text\": \":loudspeaker: *${{ github.actor }}* has deployed *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}* from *${{ steps.extract_branch.outputs.branch }}* ${{ env.SUCCESS_MESSAGE }}\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"00ff00\",\n      \"fields\": [\n        {\n          \"title\": \"What was deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"Deployed\"\n        },\n        {\n          \"title\": \"Output\",\n          \"short\": false,\n          \"value\": \"${{ env.SUCCESS_MESSAGE_BODY }}\"\n        }\n      ]\n    }\n  ]\n}\n"
-                                         )
-                                       , ( "update-ts" , "${{ steps.slack-deploy.outputs.ts }}" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "channel-id"
+                                            , UnstructuredValueString "${{ env.SLACK_CHANNEL_ID }}"
+                                            )
+                                          , ( "payload"
+                                            , UnstructuredValueString
+                                                "{\n  \"text\": \":loudspeaker: *${{ github.actor }}* has deployed *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}* from *${{ steps.extract_branch.outputs.branch }}* ${{ env.SUCCESS_MESSAGE }}\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"00ff00\",\n      \"fields\": [\n        {\n          \"title\": \"What was deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"Deployed\"\n        },\n        {\n          \"title\": \"Output\",\n          \"short\": false,\n          \"value\": \"${{ env.SUCCESS_MESSAGE_BODY }}\"\n        }\n      ]\n    }\n  ]\n}\n"
+                                            )
+                                          , ( "update-ts"
+                                            , UnstructuredValueString
+                                                "${{ steps.slack-deploy.outputs.ts }}"
+                                            )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -755,7 +809,8 @@
                            , run = Nothing
                            , runIf =
                                Just
-                                 "failure() && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master')"
+                                 (RunIfString
+                                    "failure() && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master')")
                            , shell = Nothing
                            , stepId = Just (StepId "slack-deploy-failed")
                            , timeoutMinutes = Nothing
@@ -765,13 +820,20 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "channel-id" , "${{ env.SLACK_CHANNEL_ID }}" )
-                                       , ( "payload"
-                                         , "{ \"text\": \":loudspeaker: *BUILD/DEPLOY FAILURE* of *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}*\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"ff0000\",\n      \"fields\": [\n        {\n          \"title\": \"What was being deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"Failed\"\n        },\n        {\n          \"title\": \"Who broke it\",\n          \"value\": \"${{ github.actor }}\"\n        },\n        {\n          \"title\": \"Output\",\n          \"short\": false,\n          \"value\": \"https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks\"\n        }\n      ]\n    }\n  ]\n}\n"
-                                         )
-                                       , ( "update-ts" , "${{ steps.slack-deploy.outputs.ts }}" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "channel-id"
+                                            , UnstructuredValueString "${{ env.SLACK_CHANNEL_ID }}"
+                                            )
+                                          , ( "payload"
+                                            , UnstructuredValueString
+                                                "{ \"text\": \":loudspeaker: *BUILD/DEPLOY FAILURE* of *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}*\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"ff0000\",\n      \"fields\": [\n        {\n          \"title\": \"What was being deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"Failed\"\n        },\n        {\n          \"title\": \"Who broke it\",\n          \"value\": \"${{ github.actor }}\"\n        },\n        {\n          \"title\": \"Output\",\n          \"short\": false,\n          \"value\": \"https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks\"\n        }\n      ]\n    }\n  ]\n}\n"
+                                            )
+                                          , ( "update-ts"
+                                            , UnstructuredValueString
+                                                "${{ steps.slack-deploy.outputs.ts }}"
+                                            )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -781,7 +843,7 @@
                            , run =
                                Just
                                  "gh pr comment ${{ env.PR_NUMBER }} --body-file /tmp/pr-comment.txt || true"
-                           , runIf = Just "always() && env.PR_NUMBER != 0"
+                           , runIf = Just (RunIfString "always() && env.PR_NUMBER != 0")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -827,11 +889,17 @@
                        , with =
                            Just
                              (StepWithEnv
-                                (fromList
-                                   [ ( "fetch-depth" , "1" )
-                                   , ( "path" , "${{ env.GITHUB_WORKSPACE }}" )
-                                   , ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )
-                                   ]))
+                                (UnstructuredMap
+                                   (fromList
+                                      [ ( "fetch-depth" , UnstructuredValueString "1" )
+                                      , ( "path"
+                                        , UnstructuredValueString "${{ env.GITHUB_WORKSPACE }}"
+                                        )
+                                      , ( "token"
+                                        , UnstructuredValueString
+                                            "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"
+                                        )
+                                      ])))
                        , workingDirectory = Nothing
                        } :|
                        [ Step
@@ -861,7 +929,12 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList [ ( "bellroy-nix-cache-access" , "none" ) ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "bellroy-nix-cache-access"
+                                            , UnstructuredValueString "none"
+                                            )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -877,11 +950,15 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )
-                                       , ( "tool-name" , "gitapult" )
-                                       , ( "tool-version" , "latest" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "token"
+                                            , UnstructuredValueString
+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"
+                                            )
+                                          , ( "tool-name" , UnstructuredValueString "gitapult" )
+                                          , ( "tool-version" , UnstructuredValueString "latest" )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -897,11 +974,15 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )
-                                       , ( "tool-name" , "powerful-owl" )
-                                       , ( "tool-version" , "latest" )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "token"
+                                            , UnstructuredValueString
+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"
+                                            )
+                                          , ( "tool-name" , UnstructuredValueString "powerful-owl" )
+                                          , ( "tool-version" , UnstructuredValueString "latest" )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        , Step
@@ -968,7 +1049,7 @@
                            , run =
                                Just
                                  "gh pr comment ${{ env.PR_NUMBER }} --body-file /tmp/pr-comment.txt || true"
-                           , runIf = Just "always() && env.PR_NUMBER != 0"
+                           , runIf = Just (RunIfString "always() && env.PR_NUMBER != 0")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -983,7 +1064,8 @@
                            , run = Nothing
                            , runIf =
                                Just
-                                 "failure() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable')"
+                                 (RunIfString
+                                    "failure() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable')")
                            , shell = Nothing
                            , stepId = Nothing
                            , timeoutMinutes = Nothing
@@ -991,17 +1073,24 @@
                            , with =
                                Just
                                  (StepWithEnv
-                                    (fromList
-                                       [ ( "branch"
-                                         , "${{ steps.checkout_branch.outputs.branch }}"
-                                         )
-                                       , ( "channel-id" , "${{ env.SLACK_CHANNEL_ID }}" )
-                                       , ( "message-type" , "build-fail" )
-                                       , ( "show-authors" , "true" )
-                                       , ( "slack-bot-token"
-                                         , "${{ secrets.BELLROY_SLACK_TOKEN }}"
-                                         )
-                                       ]))
+                                    (UnstructuredMap
+                                       (fromList
+                                          [ ( "branch"
+                                            , UnstructuredValueString
+                                                "${{ steps.checkout_branch.outputs.branch }}"
+                                            )
+                                          , ( "channel-id"
+                                            , UnstructuredValueString "${{ env.SLACK_CHANNEL_ID }}"
+                                            )
+                                          , ( "message-type"
+                                            , UnstructuredValueString "build-fail"
+                                            )
+                                          , ( "show-authors" , UnstructuredValueString "true" )
+                                          , ( "slack-bot-token"
+                                            , UnstructuredValueString
+                                                "${{ secrets.BELLROY_SLACK_TOKEN }}"
+                                            )
+                                          ])))
                            , workingDirectory = Nothing
                            }
                        ])
