-- SPDX-FileCopyrightText: 2026 Alexandra de Wit
--
-- SPDX-License-Identifier: MIT
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}

module Ecluse.Config.Types (
    Url (..),
    mkUrl,
    unUrl,
    MirrorCredential (..),
    MountConfig (..),
    AppConfig (..),
    ServerSettings (..),
    QueueSettings (..),
    LimitsSettings (..),
    CacheSettings (..),
    IntegritySettings (..),
    EgressSettings (..),
    AdvisoriesSettings (..),
    RuntimeSettings (..),
    ObservabilitySettings (..),
    MountRegistries (..),
    MountMode (..),
    MirroredLegs (..),
    regPrivateUpstream,
    regMirrorTarget,
    MirrorTarget (..),
    Mount (..),
    MountMap,
    Config (..),
    ConfigError (..),
    renderConfigError,
) where

import Data.IP (IPRange)
import Data.Text qualified as T
import Data.Time (NominalDiffTime)

import Ecluse.Config.Resolve (envSpellingOf, mountEnvKey)
import Ecluse.Config.Rule (PolicyError, RulePatch, renderPolicyError)
import Ecluse.Core.Credential (Secret)
import Ecluse.Core.Ecosystem (Ecosystem, ecosystemName)
import Ecluse.Core.Package (Scope)
import Ecluse.Core.Package.Integrity (MinIntegrity, MinTrustedIntegrity)
import Ecluse.Core.Package.Merge (DivergencePolicy)
import Ecluse.Core.Rules.Types (PrecededRule)
import Ecluse.Core.Security.Egress (RegistryUrl)
import Ecluse.Runtime.Credential.CodeArtifact (CodeArtifactConfig)
import Ecluse.Runtime.Log (LogFormat)
import Ecluse.Runtime.Telemetry (TelemetrySwitch)

newtype Url = Url Text
    deriving stock (Url -> Url -> Bool
(Url -> Url -> Bool) -> (Url -> Url -> Bool) -> Eq Url
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Url -> Url -> Bool
== :: Url -> Url -> Bool
$c/= :: Url -> Url -> Bool
/= :: Url -> Url -> Bool
Eq, Eq Url
Eq Url =>
(Url -> Url -> Ordering)
-> (Url -> Url -> Bool)
-> (Url -> Url -> Bool)
-> (Url -> Url -> Bool)
-> (Url -> Url -> Bool)
-> (Url -> Url -> Url)
-> (Url -> Url -> Url)
-> Ord Url
Url -> Url -> Bool
Url -> Url -> Ordering
Url -> Url -> Url
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Url -> Url -> Ordering
compare :: Url -> Url -> Ordering
$c< :: Url -> Url -> Bool
< :: Url -> Url -> Bool
$c<= :: Url -> Url -> Bool
<= :: Url -> Url -> Bool
$c> :: Url -> Url -> Bool
> :: Url -> Url -> Bool
$c>= :: Url -> Url -> Bool
>= :: Url -> Url -> Bool
$cmax :: Url -> Url -> Url
max :: Url -> Url -> Url
$cmin :: Url -> Url -> Url
min :: Url -> Url -> Url
Ord, Int -> Url -> ShowS
[Url] -> ShowS
Url -> String
(Int -> Url -> ShowS)
-> (Url -> String) -> ([Url] -> ShowS) -> Show Url
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Url -> ShowS
showsPrec :: Int -> Url -> ShowS
$cshow :: Url -> String
show :: Url -> String
$cshowList :: [Url] -> ShowS
showList :: [Url] -> ShowS
Show)

mkUrl :: Text -> Either Text Url
mkUrl :: Text -> Either Text Url
mkUrl Text
raw =
    let trimmed :: Text
trimmed = Text -> Text
T.strip Text
raw
     in if Text -> Bool
T.null Text
trimmed
            then Text -> Either Text Url
forall a b. a -> Either a b
Left Text
"expected a non-empty URL"
            else Url -> Either Text Url
forall a b. b -> Either a b
Right (Text -> Url
Url Text
trimmed)

unUrl :: Url -> Text
unUrl :: Url -> Text
unUrl (Url Text
u) = Text
u

{- | The mirror-write credential, __derived from the mirror-target URL__ so a token
can never be paired with an endpoint it was not minted for. A CodeArtifact endpoint
encodes its whole identity in its host, so that identity is parsed straight from the
URL; any other host is written with an operator-supplied static bearer. The choice is
made once, at config load ('Ecluse.Config.MirrorCredential.resolveMirrorCredential'),
and carried here so the pairing is correct by construction.
-}
data MirrorCredential
    = -- | A CodeArtifact mirror target: the mint identity parsed from its host.
      MirrorCodeArtifact CodeArtifactConfig
    | -- | Any other mirror target: an operator-supplied static write token.
      MirrorStatic Secret
    deriving stock (MirrorCredential -> MirrorCredential -> Bool
(MirrorCredential -> MirrorCredential -> Bool)
-> (MirrorCredential -> MirrorCredential -> Bool)
-> Eq MirrorCredential
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MirrorCredential -> MirrorCredential -> Bool
== :: MirrorCredential -> MirrorCredential -> Bool
$c/= :: MirrorCredential -> MirrorCredential -> Bool
/= :: MirrorCredential -> MirrorCredential -> Bool
Eq, Int -> MirrorCredential -> ShowS
[MirrorCredential] -> ShowS
MirrorCredential -> String
(Int -> MirrorCredential -> ShowS)
-> (MirrorCredential -> String)
-> ([MirrorCredential] -> ShowS)
-> Show MirrorCredential
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MirrorCredential -> ShowS
showsPrec :: Int -> MirrorCredential -> ShowS
$cshow :: MirrorCredential -> String
show :: MirrorCredential -> String
$cshowList :: [MirrorCredential] -> ShowS
showList :: [MirrorCredential] -> ShowS
Show)

data MountConfig = MountConfig
    { MountConfig -> Maybe Bool
mntEnabled :: Maybe Bool
    {- ^ The mount's explicit on\/off switch. Any operator-declared key under the
    mount activates it, so @enabled: true@ exists for the mount that needs no other
    key (a serve-only pure public gate on the template public upstream), and
    @enabled: false@ switches off a mount whose other keys remain in place.
    -}
    , MountConfig -> Maybe RegistryUrl
mntPrivateUpstream :: Maybe RegistryUrl
    , MountConfig -> RegistryUrl
mntPublicUpstream :: RegistryUrl
    , MountConfig -> Maybe RegistryUrl
mntMirrorTarget :: Maybe RegistryUrl
    , MountConfig -> Maybe Secret
mntMirrorTargetToken :: Maybe Secret
    , MountConfig -> Maybe Natural
mntMirrorCodeArtifactTokenDuration :: Maybe Natural
    , MountConfig -> Maybe RegistryUrl
mntPublicationTarget :: Maybe RegistryUrl
    , MountConfig -> Maybe Secret
mntPublicationTargetToken :: Maybe Secret
    , MountConfig -> [Scope]
mntPublishAllow :: [Scope]
    , MountConfig -> Maybe MinTrustedIntegrity
mntMinTrustedIntegrity :: Maybe MinTrustedIntegrity
    {- ^ A per-mount refinement of the global trusted-integrity floor, for the one
    legacy private registry whose loosening must not leak onto other mounts.
    -}
    , MountConfig -> Maybe DivergencePolicy
mntDivergencePolicy :: Maybe DivergencePolicy
    -- ^ A per-mount refinement of the global cross-upstream divergence policy.
    , MountConfig -> RulePatch
mntAdditionalRules :: RulePatch
    }
    deriving stock (MountConfig -> MountConfig -> Bool
(MountConfig -> MountConfig -> Bool)
-> (MountConfig -> MountConfig -> Bool) -> Eq MountConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MountConfig -> MountConfig -> Bool
== :: MountConfig -> MountConfig -> Bool
$c/= :: MountConfig -> MountConfig -> Bool
/= :: MountConfig -> MountConfig -> Bool
Eq, Int -> MountConfig -> ShowS
[MountConfig] -> ShowS
MountConfig -> String
(Int -> MountConfig -> ShowS)
-> (MountConfig -> String)
-> ([MountConfig] -> ShowS)
-> Show MountConfig
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MountConfig -> ShowS
showsPrec :: Int -> MountConfig -> ShowS
$cshow :: MountConfig -> String
show :: MountConfig -> String
$cshowList :: [MountConfig] -> ShowS
showList :: [MountConfig] -> ShowS
Show)

{- | The resolved application configuration, one sub-record per document group so a
field's home says what it governs (the document schema and this type mirror each
other one to one).
-}
data AppConfig = AppConfig
    { AppConfig -> ServerSettings
cfgServer :: ServerSettings
    , AppConfig -> QueueSettings
cfgQueue :: QueueSettings
    , AppConfig -> LimitsSettings
cfgLimits :: LimitsSettings
    , AppConfig -> CacheSettings
cfgCache :: CacheSettings
    , AppConfig -> IntegritySettings
cfgIntegrity :: IntegritySettings
    , AppConfig -> EgressSettings
cfgEgress :: EgressSettings
    , AppConfig -> AdvisoriesSettings
cfgAdvisories :: AdvisoriesSettings
    , AppConfig -> RuntimeSettings
cfgRuntime :: RuntimeSettings
    , AppConfig -> ObservabilitySettings
cfgObservability :: ObservabilitySettings
    , AppConfig -> Map Ecosystem MountConfig
cfgMounts :: Map Ecosystem MountConfig
    }
    deriving stock (AppConfig -> AppConfig -> Bool
(AppConfig -> AppConfig -> Bool)
-> (AppConfig -> AppConfig -> Bool) -> Eq AppConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AppConfig -> AppConfig -> Bool
== :: AppConfig -> AppConfig -> Bool
$c/= :: AppConfig -> AppConfig -> Bool
/= :: AppConfig -> AppConfig -> Bool
Eq, Int -> AppConfig -> ShowS
[AppConfig] -> ShowS
AppConfig -> String
(Int -> AppConfig -> ShowS)
-> (AppConfig -> String)
-> ([AppConfig] -> ShowS)
-> Show AppConfig
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AppConfig -> ShowS
showsPrec :: Int -> AppConfig -> ShowS
$cshow :: AppConfig -> String
show :: AppConfig -> String
$cshowList :: [AppConfig] -> ShowS
showList :: [AppConfig] -> ShowS
Show)

-- | The @server@ group: the inbound edge Écluse itself presents.
data ServerSettings = ServerSettings
    { ServerSettings -> Int
srvPort :: Int
    , ServerSettings -> Maybe Url
srvPublicUrl :: Maybe Url
    {- ^ Required whenever a mount is active ('Ecluse.Config.loadConfig' refuses
    otherwise): served artifact URLs are rewritten against it.
    -}
    , ServerSettings -> Maybe Secret
srvAuthToken :: Maybe Secret
    , ServerSettings -> Maybe Text
srvHelpMessage :: Maybe Text
    , ServerSettings -> Int
srvShutdownDrainTimeout :: Int
    }
    deriving stock (ServerSettings -> ServerSettings -> Bool
(ServerSettings -> ServerSettings -> Bool)
-> (ServerSettings -> ServerSettings -> Bool) -> Eq ServerSettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ServerSettings -> ServerSettings -> Bool
== :: ServerSettings -> ServerSettings -> Bool
$c/= :: ServerSettings -> ServerSettings -> Bool
/= :: ServerSettings -> ServerSettings -> Bool
Eq, Int -> ServerSettings -> ShowS
[ServerSettings] -> ShowS
ServerSettings -> String
(Int -> ServerSettings -> ShowS)
-> (ServerSettings -> String)
-> ([ServerSettings] -> ShowS)
-> Show ServerSettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ServerSettings -> ShowS
showsPrec :: Int -> ServerSettings -> ShowS
$cshow :: ServerSettings -> String
show :: ServerSettings -> String
$cshowList :: [ServerSettings] -> ShowS
showList :: [ServerSettings] -> ShowS
Show)

{- | The @queue@ group: the mirror queue's destination and the in-memory rollover's
depth cap. The backend is derived from the URL's shape ("Ecluse.Config.QueueTarget"),
never named here.
-}
data QueueSettings = QueueSettings
    { QueueSettings -> Maybe Url
qsUrl :: Maybe Url
    , QueueSettings -> Maybe Int
qsMemoryMaxDepth :: Maybe Int
    -- ^ Computed from the runtime posture when unset; a configured value wins.
    }
    deriving stock (QueueSettings -> QueueSettings -> Bool
(QueueSettings -> QueueSettings -> Bool)
-> (QueueSettings -> QueueSettings -> Bool) -> Eq QueueSettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: QueueSettings -> QueueSettings -> Bool
== :: QueueSettings -> QueueSettings -> Bool
$c/= :: QueueSettings -> QueueSettings -> Bool
/= :: QueueSettings -> QueueSettings -> Bool
Eq, Int -> QueueSettings -> ShowS
[QueueSettings] -> ShowS
QueueSettings -> String
(Int -> QueueSettings -> ShowS)
-> (QueueSettings -> String)
-> ([QueueSettings] -> ShowS)
-> Show QueueSettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> QueueSettings -> ShowS
showsPrec :: Int -> QueueSettings -> ShowS
$cshow :: QueueSettings -> String
show :: QueueSettings -> String
$cshowList :: [QueueSettings] -> ShowS
showList :: [QueueSettings] -> ShowS
Show)

{- | The @limits@ group: the hostile-input bounds. The structural counts are pinned
policy defaults; the byte-valued caps are computed from the memory plan when
unset ("Ecluse.Composition.MemoryPlan"), a configured value always winning.
-}
data LimitsSettings = LimitsSettings
    { LimitsSettings -> Maybe Int
limMaxResponseBytes :: Maybe Int
    , LimitsSettings -> Int
limMaxVersionCount :: Int
    , LimitsSettings -> Int
limMaxNestingDepth :: Int
    , LimitsSettings -> Maybe Int
limMaxRequestBytes :: Maybe Int
    , LimitsSettings -> Maybe Int
limMaxArtifactBytes :: Maybe Int
    {- ^ The mirror worker's per-artifact fetch byte cap; computed from the memory
    plan's mirror-artifact tenant when unset, a configured value winning.
    -}
    }
    deriving stock (LimitsSettings -> LimitsSettings -> Bool
(LimitsSettings -> LimitsSettings -> Bool)
-> (LimitsSettings -> LimitsSettings -> Bool) -> Eq LimitsSettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: LimitsSettings -> LimitsSettings -> Bool
== :: LimitsSettings -> LimitsSettings -> Bool
$c/= :: LimitsSettings -> LimitsSettings -> Bool
/= :: LimitsSettings -> LimitsSettings -> Bool
Eq, Int -> LimitsSettings -> ShowS
[LimitsSettings] -> ShowS
LimitsSettings -> String
(Int -> LimitsSettings -> ShowS)
-> (LimitsSettings -> String)
-> ([LimitsSettings] -> ShowS)
-> Show LimitsSettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> LimitsSettings -> ShowS
showsPrec :: Int -> LimitsSettings -> ShowS
$cshow :: LimitsSettings -> String
show :: LimitsSettings -> String
$cshowList :: [LimitsSettings] -> ShowS
showList :: [LimitsSettings] -> ShowS
Show)

-- | The @cache@ group: the metadata cache's TTL and its computed-by-default bounds.
data CacheSettings = CacheSettings
    { CacheSettings -> NominalDiffTime
csTtl :: NominalDiffTime
    , CacheSettings -> Maybe Int
csMaxEntries :: Maybe Int
    -- ^ Computed from the runtime posture when unset; a configured value wins.
    , CacheSettings -> Maybe Int
csMaxBytes :: Maybe Int
    -- ^ Computed from the runtime posture when unset; a configured value wins.
    }
    deriving stock (CacheSettings -> CacheSettings -> Bool
(CacheSettings -> CacheSettings -> Bool)
-> (CacheSettings -> CacheSettings -> Bool) -> Eq CacheSettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CacheSettings -> CacheSettings -> Bool
== :: CacheSettings -> CacheSettings -> Bool
$c/= :: CacheSettings -> CacheSettings -> Bool
/= :: CacheSettings -> CacheSettings -> Bool
Eq, Int -> CacheSettings -> ShowS
[CacheSettings] -> ShowS
CacheSettings -> String
(Int -> CacheSettings -> ShowS)
-> (CacheSettings -> String)
-> ([CacheSettings] -> ShowS)
-> Show CacheSettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CacheSettings -> ShowS
showsPrec :: Int -> CacheSettings -> ShowS
$cshow :: CacheSettings -> String
show :: CacheSettings -> String
$cshowList :: [CacheSettings] -> ShowS
showList :: [CacheSettings] -> ShowS
Show)

{- | The @integrity@ group: the global integrity floors and divergence policy
(@minTrusted@ and @divergencePolicy@ refinable per mount).
-}
data IntegritySettings = IntegritySettings
    { IntegritySettings -> MinIntegrity
intMinPublic :: MinIntegrity
    , IntegritySettings -> MinTrustedIntegrity
intMinTrusted :: MinTrustedIntegrity
    , IntegritySettings -> DivergencePolicy
intDivergencePolicy :: DivergencePolicy
    }
    deriving stock (IntegritySettings -> IntegritySettings -> Bool
(IntegritySettings -> IntegritySettings -> Bool)
-> (IntegritySettings -> IntegritySettings -> Bool)
-> Eq IntegritySettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: IntegritySettings -> IntegritySettings -> Bool
== :: IntegritySettings -> IntegritySettings -> Bool
$c/= :: IntegritySettings -> IntegritySettings -> Bool
/= :: IntegritySettings -> IntegritySettings -> Bool
Eq, Int -> IntegritySettings -> ShowS
[IntegritySettings] -> ShowS
IntegritySettings -> String
(Int -> IntegritySettings -> ShowS)
-> (IntegritySettings -> String)
-> ([IntegritySettings] -> ShowS)
-> Show IntegritySettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> IntegritySettings -> ShowS
showsPrec :: Int -> IntegritySettings -> ShowS
$cshow :: IntegritySettings -> String
show :: IntegritySettings -> String
$cshowList :: [IntegritySettings] -> ShowS
showList :: [IntegritySettings] -> ShowS
Show)

-- | The @egress@ group: the operator's additions to the blocked target ranges.
newtype EgressSettings = EgressSettings
    { EgressSettings -> [IPRange]
egrAdditionalBlockedRanges :: [IPRange]
    }
    deriving stock (EgressSettings -> EgressSettings -> Bool
(EgressSettings -> EgressSettings -> Bool)
-> (EgressSettings -> EgressSettings -> Bool) -> Eq EgressSettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: EgressSettings -> EgressSettings -> Bool
== :: EgressSettings -> EgressSettings -> Bool
$c/= :: EgressSettings -> EgressSettings -> Bool
/= :: EgressSettings -> EgressSettings -> Bool
Eq, Int -> EgressSettings -> ShowS
[EgressSettings] -> ShowS
EgressSettings -> String
(Int -> EgressSettings -> ShowS)
-> (EgressSettings -> String)
-> ([EgressSettings] -> ShowS)
-> Show EgressSettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> EgressSettings -> ShowS
showsPrec :: Int -> EgressSettings -> ShowS
$cshow :: EgressSettings -> String
show :: EgressSettings -> String
$cshowList :: [EgressSettings] -> ShowS
showList :: [EgressSettings] -> ShowS
Show)

-- | The @advisories@ group: the OSV/CVE pipeline's bucket, cadences, and bounds.
data AdvisoriesSettings = AdvisoriesSettings
    { AdvisoriesSettings -> Maybe Text
advBucket :: Maybe Text
    , AdvisoriesSettings -> NominalDiffTime
advPollInterval :: NominalDiffTime
    , AdvisoriesSettings -> NominalDiffTime
advCompileInterval :: NominalDiffTime
    , AdvisoriesSettings -> String
advDataDir :: FilePath
    , AdvisoriesSettings -> Text
advOsvExportBaseUrl :: Text
    , AdvisoriesSettings -> Int
advMaxDatabaseBytes :: Int
    }
    deriving stock (AdvisoriesSettings -> AdvisoriesSettings -> Bool
(AdvisoriesSettings -> AdvisoriesSettings -> Bool)
-> (AdvisoriesSettings -> AdvisoriesSettings -> Bool)
-> Eq AdvisoriesSettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AdvisoriesSettings -> AdvisoriesSettings -> Bool
== :: AdvisoriesSettings -> AdvisoriesSettings -> Bool
$c/= :: AdvisoriesSettings -> AdvisoriesSettings -> Bool
/= :: AdvisoriesSettings -> AdvisoriesSettings -> Bool
Eq, Int -> AdvisoriesSettings -> ShowS
[AdvisoriesSettings] -> ShowS
AdvisoriesSettings -> String
(Int -> AdvisoriesSettings -> ShowS)
-> (AdvisoriesSettings -> String)
-> ([AdvisoriesSettings] -> ShowS)
-> Show AdvisoriesSettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AdvisoriesSettings -> ShowS
showsPrec :: Int -> AdvisoriesSettings -> ShowS
$cshow :: AdvisoriesSettings -> String
show :: AdvisoriesSettings -> String
$cshowList :: [AdvisoriesSettings] -> ShowS
showList :: [AdvisoriesSettings] -> ShowS
Show)

{- | The @runtime@ group: the process-sizing overrides. Every field is optional;
unset, each is computed from the runtime posture (cgroups, RTS, file-descriptor
limit) with its provenance boot-logged.
-}
data RuntimeSettings = RuntimeSettings
    { RuntimeSettings -> Maybe Int
rtCores :: Maybe Int
    , RuntimeSettings -> Maybe Int
rtMaxHeapBytes :: Maybe Int
    , RuntimeSettings -> Maybe Int
rtServeMaxInFlight :: Maybe Int
    , RuntimeSettings -> Maybe Int
rtPublicConnectionsPerHost :: Maybe Int
    , RuntimeSettings -> Maybe Int
rtPrivateConnectionsPerHost :: Maybe Int
    }
    deriving stock (RuntimeSettings -> RuntimeSettings -> Bool
(RuntimeSettings -> RuntimeSettings -> Bool)
-> (RuntimeSettings -> RuntimeSettings -> Bool)
-> Eq RuntimeSettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RuntimeSettings -> RuntimeSettings -> Bool
== :: RuntimeSettings -> RuntimeSettings -> Bool
$c/= :: RuntimeSettings -> RuntimeSettings -> Bool
/= :: RuntimeSettings -> RuntimeSettings -> Bool
Eq, Int -> RuntimeSettings -> ShowS
[RuntimeSettings] -> ShowS
RuntimeSettings -> String
(Int -> RuntimeSettings -> ShowS)
-> (RuntimeSettings -> String)
-> ([RuntimeSettings] -> ShowS)
-> Show RuntimeSettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RuntimeSettings -> ShowS
showsPrec :: Int -> RuntimeSettings -> ShowS
$cshow :: RuntimeSettings -> String
show :: RuntimeSettings -> String
$cshowList :: [RuntimeSettings] -> ShowS
showList :: [RuntimeSettings] -> ShowS
Show)

-- | The @observability@ group: log shape and telemetry switch.
data ObservabilitySettings = ObservabilitySettings
    { ObservabilitySettings -> LogFormat
obsLogFormat :: LogFormat
    , ObservabilitySettings -> TelemetrySwitch
obsTelemetry :: TelemetrySwitch
    }
    deriving stock (ObservabilitySettings -> ObservabilitySettings -> Bool
(ObservabilitySettings -> ObservabilitySettings -> Bool)
-> (ObservabilitySettings -> ObservabilitySettings -> Bool)
-> Eq ObservabilitySettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ObservabilitySettings -> ObservabilitySettings -> Bool
== :: ObservabilitySettings -> ObservabilitySettings -> Bool
$c/= :: ObservabilitySettings -> ObservabilitySettings -> Bool
/= :: ObservabilitySettings -> ObservabilitySettings -> Bool
Eq, Int -> ObservabilitySettings -> ShowS
[ObservabilitySettings] -> ShowS
ObservabilitySettings -> String
(Int -> ObservabilitySettings -> ShowS)
-> (ObservabilitySettings -> String)
-> ([ObservabilitySettings] -> ShowS)
-> Show ObservabilitySettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ObservabilitySettings -> ShowS
showsPrec :: Int -> ObservabilitySettings -> ShowS
$cshow :: ObservabilitySettings -> String
show :: ObservabilitySettings -> String
$cshowList :: [ObservabilitySettings] -> ShowS
showList :: [ObservabilitySettings] -> ShowS
Show)

data MountRegistries = MountRegistries
    { MountRegistries -> RegistryUrl
regPublicUpstream :: RegistryUrl
    , MountRegistries -> MountMode
regMode :: MountMode
    }
    deriving stock (MountRegistries -> MountRegistries -> Bool
(MountRegistries -> MountRegistries -> Bool)
-> (MountRegistries -> MountRegistries -> Bool)
-> Eq MountRegistries
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MountRegistries -> MountRegistries -> Bool
== :: MountRegistries -> MountRegistries -> Bool
$c/= :: MountRegistries -> MountRegistries -> Bool
/= :: MountRegistries -> MountRegistries -> Bool
Eq, Int -> MountRegistries -> ShowS
[MountRegistries] -> ShowS
MountRegistries -> String
(Int -> MountRegistries -> ShowS)
-> (MountRegistries -> String)
-> ([MountRegistries] -> ShowS)
-> Show MountRegistries
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MountRegistries -> ShowS
showsPrec :: Int -> MountRegistries -> ShowS
$cshow :: MountRegistries -> String
show :: MountRegistries -> String
$cshowList :: [MountRegistries] -> ShowS
showList :: [MountRegistries] -> ShowS
Show)

{- | Whether a mount mirrors, derived from its declared endpoints: a declared
@mirrorTarget@ makes the mount 'Mirrored' (and its private upstream is then required,
so the mirror can be read back), an absent one makes it 'ServeOnly' (never writes
anywhere; the private upstream is optional, and a mount with neither is the pure
public gate). The coupling is structural, so a mirrored mount without a readable
private leg is unrepresentable.
-}
data MountMode
    = -- | The mount mirrors admitted public artifacts; both legs are required.
      Mirrored MirroredLegs
    | -- | The mount never writes; the optional private upstream is still merged when present.
      ServeOnly (Maybe RegistryUrl)
    deriving stock (MountMode -> MountMode -> Bool
(MountMode -> MountMode -> Bool)
-> (MountMode -> MountMode -> Bool) -> Eq MountMode
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MountMode -> MountMode -> Bool
== :: MountMode -> MountMode -> Bool
$c/= :: MountMode -> MountMode -> Bool
/= :: MountMode -> MountMode -> Bool
Eq, Int -> MountMode -> ShowS
[MountMode] -> ShowS
MountMode -> String
(Int -> MountMode -> ShowS)
-> (MountMode -> String)
-> ([MountMode] -> ShowS)
-> Show MountMode
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MountMode -> ShowS
showsPrec :: Int -> MountMode -> ShowS
$cshow :: MountMode -> String
show :: MountMode -> String
$cshowList :: [MountMode] -> ShowS
showList :: [MountMode] -> ShowS
Show)

{- | A mirrored mount's two required halves: the readable private upstream and the
mirror target married to its derived write credential.
-}
data MirroredLegs = MirroredLegs
    { MirroredLegs -> RegistryUrl
mlPrivateUpstream :: RegistryUrl
    , MirroredLegs -> MirrorTarget
mlMirrorTarget :: MirrorTarget
    }
    deriving stock (MirroredLegs -> MirroredLegs -> Bool
(MirroredLegs -> MirroredLegs -> Bool)
-> (MirroredLegs -> MirroredLegs -> Bool) -> Eq MirroredLegs
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MirroredLegs -> MirroredLegs -> Bool
== :: MirroredLegs -> MirroredLegs -> Bool
$c/= :: MirroredLegs -> MirroredLegs -> Bool
/= :: MirroredLegs -> MirroredLegs -> Bool
Eq, Int -> MirroredLegs -> ShowS
[MirroredLegs] -> ShowS
MirroredLegs -> String
(Int -> MirroredLegs -> ShowS)
-> (MirroredLegs -> String)
-> ([MirroredLegs] -> ShowS)
-> Show MirroredLegs
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MirroredLegs -> ShowS
showsPrec :: Int -> MirroredLegs -> ShowS
$cshow :: MirroredLegs -> String
show :: MirroredLegs -> String
$cshowList :: [MirroredLegs] -> ShowS
showList :: [MirroredLegs] -> ShowS
Show)

{- | The mount's private upstream, when it has one: total over both modes, so call
sites read as before while the compiler makes them face the serve-only absence.
-}
regPrivateUpstream :: MountRegistries -> Maybe RegistryUrl
regPrivateUpstream :: MountRegistries -> Maybe RegistryUrl
regPrivateUpstream MountRegistries
regs = case MountRegistries -> MountMode
regMode MountRegistries
regs of
    Mirrored MirroredLegs
legs -> RegistryUrl -> Maybe RegistryUrl
forall a. a -> Maybe a
Just (MirroredLegs -> RegistryUrl
mlPrivateUpstream MirroredLegs
legs)
    ServeOnly Maybe RegistryUrl
mPrivate -> Maybe RegistryUrl
mPrivate

-- | The mount's mirror target (with its derived credential), when it mirrors.
regMirrorTarget :: MountRegistries -> Maybe MirrorTarget
regMirrorTarget :: MountRegistries -> Maybe MirrorTarget
regMirrorTarget MountRegistries
regs = case MountRegistries -> MountMode
regMode MountRegistries
regs of
    Mirrored MirroredLegs
legs -> MirrorTarget -> Maybe MirrorTarget
forall a. a -> Maybe a
Just (MirroredLegs -> MirrorTarget
mlMirrorTarget MirroredLegs
legs)
    ServeOnly Maybe RegistryUrl
_ -> Maybe MirrorTarget
forall a. Maybe a
Nothing

data MirrorTarget = MirrorTarget
    { MirrorTarget -> RegistryUrl
mtUrl :: RegistryUrl
    , MirrorTarget -> MirrorCredential
mtCredential :: MirrorCredential
    }
    deriving stock (MirrorTarget -> MirrorTarget -> Bool
(MirrorTarget -> MirrorTarget -> Bool)
-> (MirrorTarget -> MirrorTarget -> Bool) -> Eq MirrorTarget
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MirrorTarget -> MirrorTarget -> Bool
== :: MirrorTarget -> MirrorTarget -> Bool
$c/= :: MirrorTarget -> MirrorTarget -> Bool
/= :: MirrorTarget -> MirrorTarget -> Bool
Eq, Int -> MirrorTarget -> ShowS
[MirrorTarget] -> ShowS
MirrorTarget -> String
(Int -> MirrorTarget -> ShowS)
-> (MirrorTarget -> String)
-> ([MirrorTarget] -> ShowS)
-> Show MirrorTarget
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MirrorTarget -> ShowS
showsPrec :: Int -> MirrorTarget -> ShowS
$cshow :: MirrorTarget -> String
show :: MirrorTarget -> String
$cshowList :: [MirrorTarget] -> ShowS
showList :: [MirrorTarget] -> ShowS
Show)

data Mount = Mount
    { Mount -> Ecosystem
mountEcosystem :: Ecosystem
    , Mount -> MountRegistries
mountRegistries :: MountRegistries
    , Mount -> [PrecededRule]
mountPolicy :: [PrecededRule]
    }
    deriving stock (Mount -> Mount -> Bool
(Mount -> Mount -> Bool) -> (Mount -> Mount -> Bool) -> Eq Mount
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Mount -> Mount -> Bool
== :: Mount -> Mount -> Bool
$c/= :: Mount -> Mount -> Bool
/= :: Mount -> Mount -> Bool
Eq, Int -> Mount -> ShowS
[Mount] -> ShowS
Mount -> String
(Int -> Mount -> ShowS)
-> (Mount -> String) -> ([Mount] -> ShowS) -> Show Mount
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Mount -> ShowS
showsPrec :: Int -> Mount -> ShowS
$cshow :: Mount -> String
show :: Mount -> String
$cshowList :: [Mount] -> ShowS
showList :: [Mount] -> ShowS
Show)

type MountMap = Map Ecosystem Mount

data Config = Config
    { Config -> AppConfig
configApp :: AppConfig
    , Config -> MountMap
configMounts :: MountMap
    }
    deriving stock (Config -> Config -> Bool
(Config -> Config -> Bool)
-> (Config -> Config -> Bool) -> Eq Config
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Config -> Config -> Bool
== :: Config -> Config -> Bool
$c/= :: Config -> Config -> Bool
/= :: Config -> Config -> Bool
Eq, Int -> Config -> ShowS
[Config] -> ShowS
Config -> String
(Int -> Config -> ShowS)
-> (Config -> String) -> ([Config] -> ShowS) -> Show Config
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Config -> ShowS
showsPrec :: Int -> Config -> ShowS
$cshow :: Config -> String
show :: Config -> String
$cshowList :: [Config] -> ShowS
showList :: [Config] -> ShowS
Show)

data ConfigError
    = ParseError Text
    | PolicyErrors [PolicyError]
    | {- | A mount is active but @server.publicUrl@ is unset. Served artifact URLs
      must be rewritten against the proxy's own externally-reachable base URL; a
      relative @dist.tarball@ reads to the npm CLI as a @file:@ path and every
      install fails, so the omission is refused at boot rather than discovered
      client by client. Host-header derivation is deliberately not offered (a
      spoofed header would poison every shared-cache entry with an
      attacker-chosen artifact URL).
      -}
      PublicUrlRequired
    | {- | A __mirrored__ mount (one that declares a @mirrorTarget@) does not define
      its private upstream. The mirror write must be readable back through the
      private leg, so a mirrored mount without one is refused; a serve-only mount
      (no @mirrorTarget@) never raises this.
      -}
      MountMissingPrivateUpstream Ecosystem
    | {- | A serve-only mount (no @mirrorTarget@ declared) carries a mirror-write
      setting anyway. A write credential or token duration on a mount that never
      writes signals a misunderstanding (most likely a missing @mirrorTarget@), so
      it is refused per offending key rather than silently ignored. Carries the
      mount's ecosystem and the offending document key.
      -}
      MirrorSettingWithoutWrite Ecosystem Text
    | {- | An active mount's mirror target is not a CodeArtifact endpoint (whose write
      token would be minted), so it needs an explicit static write token, and none was
      supplied. Carries the mount's ecosystem.
      -}
      MirrorCredentialTokenMissing Ecosystem
    | {- | An active mount's mirror target is a CodeArtifact endpoint (its write token is
      minted automatically from the host identity) yet a static write token was also
      supplied. Refused so the two credential sources can never silently contend.
      Carries the mount's ecosystem.
      -}
      MirrorCredentialConflict Ecosystem
    deriving stock (ConfigError -> ConfigError -> Bool
(ConfigError -> ConfigError -> Bool)
-> (ConfigError -> ConfigError -> Bool) -> Eq ConfigError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ConfigError -> ConfigError -> Bool
== :: ConfigError -> ConfigError -> Bool
$c/= :: ConfigError -> ConfigError -> Bool
/= :: ConfigError -> ConfigError -> Bool
Eq, Int -> ConfigError -> ShowS
[ConfigError] -> ShowS
ConfigError -> String
(Int -> ConfigError -> ShowS)
-> (ConfigError -> String)
-> ([ConfigError] -> ShowS)
-> Show ConfigError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ConfigError -> ShowS
showsPrec :: Int -> ConfigError -> ShowS
$cshow :: ConfigError -> String
show :: ConfigError -> String
$cshowList :: [ConfigError] -> ShowS
showList :: [ConfigError] -> ShowS
Show)

renderConfigError :: ConfigError -> Text
renderConfigError :: ConfigError -> Text
renderConfigError (ParseError Text
e) = Text
e
renderConfigError (PolicyErrors [PolicyError]
es) = [Text] -> Text
T.unlines ((PolicyError -> Text) -> [PolicyError] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map PolicyError -> Text
renderPolicyError [PolicyError]
es)
renderConfigError ConfigError
PublicUrlRequired =
    Text
"a mount is active but server.publicUrl (ECLUSE_SERVER__PUBLIC_URL) is not set: "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"served tarball URLs are rewritten against the proxy's own externally-reachable base URL, "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"and without one the npm CLI reads the relative dist.tarball as a file: path and every install fails; "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"set it to the URL clients reach this proxy on (e.g. https://registry.example.com)"
renderConfigError (MountMissingPrivateUpstream Ecosystem
eco) =
    let name :: Text
name = Ecosystem -> Text
ecosystemName Ecosystem
eco
        envKey :: Text
envKey = Text -> Text -> Text
mountEnvKey Text
name (Text -> Text
envSpellingOf Text
"privateUpstream")
     in Text
"mount \""
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\" declares a mirror target, so it must also define the private upstream the mirror is read back through: set mounts."
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".privateUpstream in the config document (or "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
envKey
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"), or remove mounts."
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".mirrorTarget for a serve-only mount that never mirrors"
renderConfigError (MirrorSettingWithoutWrite Ecosystem
eco Text
key) =
    let name :: Text
name = Ecosystem -> Text
ecosystemName Ecosystem
eco
        envKey :: Text
envKey = Text -> Text -> Text
mountEnvKey Text
name (Text -> Text
envSpellingOf Text
key)
     in Text
"mount \""
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\" declares no mirror target, so mounts."
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"."
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
key
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ("
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
envKey
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
") has nothing to write with: set mounts."
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".mirrorTarget to mirror, or remove the setting for a serve-only mount"
renderConfigError (MirrorCredentialTokenMissing Ecosystem
eco) =
    let name :: Text
name = Ecosystem -> Text
ecosystemName Ecosystem
eco
        envKey :: Text
envKey = Text -> Text -> Text
mountEnvKey Text
name (Text -> Text
envSpellingOf Text
"mirrorTargetToken")
     in Text
"mount \""
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\" mirror target is not a CodeArtifact endpoint, so its write credential is not minted: set a static write token with mounts."
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".mirrorTargetToken (or "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
envKey
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
renderConfigError (MirrorCredentialConflict Ecosystem
eco) =
    let name :: Text
name = Ecosystem -> Text
ecosystemName Ecosystem
eco
        envKey :: Text
envKey = Text -> Text -> Text
mountEnvKey Text
name (Text -> Text
envSpellingOf Text
"mirrorTargetToken")
     in Text
"mount \""
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\" mirror target is a CodeArtifact endpoint (its write token is minted from the host identity), so a static write token must not also be set: remove mounts."
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".mirrorTargetToken (or "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
envKey
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"