-- SPDX-FileCopyrightText: 2026 Alexandra de Wit
--
-- SPDX-License-Identifier: MIT

{- | The configuration vocabulary: the settings records a load resolves to, the refusals their
URL-valued keys carry, and the errors a refused load reports.

Every type here is shared between the decoders ("Ecluse.Config.Parser", "Ecluse.Config.Aeson") and
the composition root that reads them, so neither side imports the other. "Ecluse.Config" assembles
them into a 'Config'.
-}
module Ecluse.Config.Types (
    Url,
    mkUrl,
    unUrl,
    HttpScheme (..),
    splitHttpScheme,
    StoreTag (..),
    storeTagName,
    Target (..),
    PrivateEndpoint (..),
    DeletionConsent (..),
    MirrorWrite (..),
    MirrorEndpoint (..),
    meTarget,
    PublicationEndpoint (..),
    MintPlan (..),
    ControlPlane (..),
    StoreBackend (..),
    sbTag,
    sbMint,
    sbControl,
    FirstParty (..),
    MountIntegrity (..),
    MountConfig (..),
    AppConfig (..),
    ServerSettings (..),
    QueueTarget (..),
    QueueUrl,
    queueUrlText,
    queueUrlTarget,
    QueueSettings (..),
    AdvisoryStoreTarget (..),
    AdvisoryStoreUrl,
    advisoryStoreUrlText,
    advisoryStoreTarget,
    LimitsSettings (..),
    CacheSettings (..),
    IntegritySettings (..),
    EgressSettings (..),
    AdvisoriesSettings (..),
    RuntimeSettings (..),
    ObservabilitySettings (..),
    DredgerSettings (..),
    QuotaOverride (..),
    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.Advisory.Internal (
    AdvisoryStoreTarget (..),
    AdvisoryStoreUrl,
    advisoryStoreTarget,
    advisoryStoreUrlText,
 )
import Ecluse.Config.Queue.Internal (QueueTarget (..), QueueUrl, queueUrlTarget, queueUrlText)
import Ecluse.Config.Resolve (mountDocRef, mountKeyRef)
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.Registry.Maintenance.Budget (QuotaDimension, RequestKind)
import Ecluse.Core.Registry.PyPI.FirstParty (PyPIFirstParty)
import Ecluse.Core.Rules.Types (PrecededRule)
import Ecluse.Core.Security (hostPortAddress, refuseCredentialMaterial)
import Ecluse.Core.Security.Egress (RegistryUrl)
import Ecluse.Runtime.Credential.CodeArtifact (CodeArtifactConfig)
import Ecluse.Runtime.Log (LogFormat, LogLevel)
import Ecluse.Runtime.Maintenance.CodeArtifact.Decide (CodeArtifactStore)
import Ecluse.Runtime.Telemetry (TelemetrySwitch)

{- | An operator-configured @http(s)@ URL, whitespace-trimmed. 'mkUrl' is the only builder, so no
value exists carrying credential material, another scheme, or an authority the egress gate misses.
-}
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)

{- | Build a 'Url' from a configuration key and the value written under it, the key naming every
refusal. The credential refusal runs first, because the two refusals below it quote the value.
-}
mkUrl :: Text -> Text -> Either Text Url
mkUrl :: Text -> Text -> Either Text Url
mkUrl Text
key Text
raw
    | Left Text
reason <- Text -> Text -> Either Text ()
refuseCredentialMaterial Text
key Text
trimmed = Text -> Either Text Url
forall a b. a -> Either a b
Left Text
reason
    | Maybe (HttpScheme, Text) -> Bool
forall a. Maybe a -> Bool
isNothing (Text -> Maybe (HttpScheme, Text)
splitHttpScheme Text
trimmed) =
        Text -> Either Text Url
forall a b. a -> Either a b
Left (Text
key Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" must be an http:// or https:// URL (got " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
trimmed Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")")
    | Maybe HostPort -> Bool
forall a. Maybe a -> Bool
isNothing (Text -> Maybe HostPort
hostPortAddress Text
trimmed) =
        Text -> Either Text Url
forall a b. a -> Either a b
Left
            ( Text
key
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" must carry a host and, when a port is written, a decimal port in 1..65535 (got "
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
trimmed
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
            )
    | Bool
otherwise = Url -> Either Text Url
forall a b. b -> Either a b
Right (Text -> Url
Url Text
trimmed)
  where
    trimmed :: Text
trimmed = Text -> Text
T.strip Text
raw

-- | The stored URL text.
unUrl :: Url -> Text
unUrl :: Url -> Text
unUrl (Url Text
u) = Text
u

-- | The scheme a configured @http(s)@ URL writes.
data HttpScheme = Http | Https
    deriving stock (HttpScheme -> HttpScheme -> Bool
(HttpScheme -> HttpScheme -> Bool)
-> (HttpScheme -> HttpScheme -> Bool) -> Eq HttpScheme
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: HttpScheme -> HttpScheme -> Bool
== :: HttpScheme -> HttpScheme -> Bool
$c/= :: HttpScheme -> HttpScheme -> Bool
/= :: HttpScheme -> HttpScheme -> Bool
Eq, Int -> HttpScheme -> ShowS
[HttpScheme] -> ShowS
HttpScheme -> String
(Int -> HttpScheme -> ShowS)
-> (HttpScheme -> String)
-> ([HttpScheme] -> ShowS)
-> Show HttpScheme
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> HttpScheme -> ShowS
showsPrec :: Int -> HttpScheme -> ShowS
$cshow :: HttpScheme -> String
show :: HttpScheme -> String
$cshowList :: [HttpScheme] -> ShowS
showList :: [HttpScheme] -> ShowS
Show)

{- | Split a URL into the scheme it writes and the text after the separator, or 'Nothing' for
neither @http@ nor @https@. It is the one scheme check the configuration layer shares.
-}
splitHttpScheme :: Text -> Maybe (HttpScheme, Text)
splitHttpScheme :: Text -> Maybe (HttpScheme, Text)
splitHttpScheme Text
raw =
    ((HttpScheme
Https,) (Text -> (HttpScheme, Text))
-> Maybe Text -> Maybe (HttpScheme, Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Text -> Maybe Text
T.stripPrefix Text
"https://" Text
raw) Maybe (HttpScheme, Text)
-> Maybe (HttpScheme, Text) -> Maybe (HttpScheme, Text)
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> ((HttpScheme
Http,) (Text -> (HttpScheme, Text))
-> Maybe Text -> Maybe (HttpScheme, Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Text -> Maybe Text
T.stripPrefix Text
"http://" Text
raw)

{- | Which store backend an endpoint names. The operator declares it as the one key under the
endpoint, and the load validates the URL against it rather than guessing it from a host shape.
-}
data StoreTag
    = -- | Any host that speaks the ecosystem's protocol, authenticated by a static token.
      TagRegistry
    | -- | A CodeArtifact repository endpoint, which mints its own write token.
      TagCodeArtifact
    | -- | A Verdaccio development store, authenticated by a static token.
      TagVerdaccio
    deriving stock (StoreTag -> StoreTag -> Bool
(StoreTag -> StoreTag -> Bool)
-> (StoreTag -> StoreTag -> Bool) -> Eq StoreTag
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: StoreTag -> StoreTag -> Bool
== :: StoreTag -> StoreTag -> Bool
$c/= :: StoreTag -> StoreTag -> Bool
/= :: StoreTag -> StoreTag -> Bool
Eq, Int -> StoreTag -> ShowS
[StoreTag] -> ShowS
StoreTag -> String
(Int -> StoreTag -> ShowS)
-> (StoreTag -> String) -> ([StoreTag] -> ShowS) -> Show StoreTag
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> StoreTag -> ShowS
showsPrec :: Int -> StoreTag -> ShowS
$cshow :: StoreTag -> String
show :: StoreTag -> String
$cshowList :: [StoreTag] -> ShowS
showList :: [StoreTag] -> ShowS
Show)

-- | The tag as an operator writes it, and as a refusal names it.
storeTagName :: StoreTag -> Text
storeTagName :: StoreTag -> Text
storeTagName = \case
    StoreTag
TagRegistry -> Text
"registry"
    StoreTag
TagCodeArtifact -> Text
"codeArtifact"
    StoreTag
TagVerdaccio -> Text
"verdaccio"

-- | An endpoint as a mount declares it: the tag naming its store, and the URL under that tag.
data Target = Target
    { Target -> StoreTag
tgtTag :: StoreTag
    , Target -> RegistryUrl
tgtUrl :: RegistryUrl
    }
    deriving stock (Target -> Target -> Bool
(Target -> Target -> Bool)
-> (Target -> Target -> Bool) -> Eq Target
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Target -> Target -> Bool
== :: Target -> Target -> Bool
$c/= :: Target -> Target -> Bool
/= :: Target -> Target -> Bool
Eq, Int -> Target -> ShowS
[Target] -> ShowS
Target -> String
(Int -> Target -> ShowS)
-> (Target -> String) -> ([Target] -> ShowS) -> Show Target
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Target -> ShowS
showsPrec :: Int -> Target -> ShowS
$cshow :: Target -> String
show :: Target -> String
$cshowList :: [Target] -> ShowS
showList :: [Target] -> ShowS
Show)

{- | Whether the operator consented to @ecluse dredger@ deleting from a Verdaccio store. It is a
declaration about that one store, so it exists under no other tag.
-}
data DeletionConsent
    = DeletionPermitted
    | DeletionWithheld
    deriving stock (DeletionConsent -> DeletionConsent -> Bool
(DeletionConsent -> DeletionConsent -> Bool)
-> (DeletionConsent -> DeletionConsent -> Bool)
-> Eq DeletionConsent
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: DeletionConsent -> DeletionConsent -> Bool
== :: DeletionConsent -> DeletionConsent -> Bool
$c/= :: DeletionConsent -> DeletionConsent -> Bool
/= :: DeletionConsent -> DeletionConsent -> Bool
Eq, Int -> DeletionConsent -> ShowS
[DeletionConsent] -> ShowS
DeletionConsent -> String
(Int -> DeletionConsent -> ShowS)
-> (DeletionConsent -> String)
-> ([DeletionConsent] -> ShowS)
-> Show DeletionConsent
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> DeletionConsent -> ShowS
showsPrec :: Int -> DeletionConsent -> ShowS
$cshow :: DeletionConsent -> String
show :: DeletionConsent -> String
$cshowList :: [DeletionConsent] -> ShowS
showList :: [DeletionConsent] -> ShowS
Show)

{- | How a mount's mirror write authenticates, one arm per tag. The mirror write is Écluse's one
standing credential, so a minting tag carries no static token and a non-minting tag requires one.
-}
data MirrorWrite
    = -- | Any protocol-speaking host: the operator's static write token.
      WriteRegistry Secret
    | -- | A CodeArtifact repository: the requested lifetime of the token it mints.
      WriteCodeArtifact (Maybe Natural)
    | -- | A Verdaccio store: its static write token, and the operator's deletion consent.
      WriteVerdaccio Secret DeletionConsent
    deriving stock (MirrorWrite -> MirrorWrite -> Bool
(MirrorWrite -> MirrorWrite -> Bool)
-> (MirrorWrite -> MirrorWrite -> Bool) -> Eq MirrorWrite
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MirrorWrite -> MirrorWrite -> Bool
== :: MirrorWrite -> MirrorWrite -> Bool
$c/= :: MirrorWrite -> MirrorWrite -> Bool
/= :: MirrorWrite -> MirrorWrite -> Bool
Eq, Int -> MirrorWrite -> ShowS
[MirrorWrite] -> ShowS
MirrorWrite -> String
(Int -> MirrorWrite -> ShowS)
-> (MirrorWrite -> String)
-> ([MirrorWrite] -> ShowS)
-> Show MirrorWrite
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MirrorWrite -> ShowS
showsPrec :: Int -> MirrorWrite -> ShowS
$cshow :: MirrorWrite -> String
show :: MirrorWrite -> String
$cshowList :: [MirrorWrite] -> ShowS
showList :: [MirrorWrite] -> ShowS
Show)

-- | A declared @mirrorTarget@: where the mirror writes, and how that write authenticates.
data MirrorEndpoint = MirrorEndpoint
    { MirrorEndpoint -> RegistryUrl
meUrl :: RegistryUrl
    , MirrorEndpoint -> MirrorWrite
meWrite :: MirrorWrite
    }
    deriving stock (MirrorEndpoint -> MirrorEndpoint -> Bool
(MirrorEndpoint -> MirrorEndpoint -> Bool)
-> (MirrorEndpoint -> MirrorEndpoint -> Bool) -> Eq MirrorEndpoint
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MirrorEndpoint -> MirrorEndpoint -> Bool
== :: MirrorEndpoint -> MirrorEndpoint -> Bool
$c/= :: MirrorEndpoint -> MirrorEndpoint -> Bool
/= :: MirrorEndpoint -> MirrorEndpoint -> Bool
Eq, Int -> MirrorEndpoint -> ShowS
[MirrorEndpoint] -> ShowS
MirrorEndpoint -> String
(Int -> MirrorEndpoint -> ShowS)
-> (MirrorEndpoint -> String)
-> ([MirrorEndpoint] -> ShowS)
-> Show MirrorEndpoint
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MirrorEndpoint -> ShowS
showsPrec :: Int -> MirrorEndpoint -> ShowS
$cshow :: MirrorEndpoint -> String
show :: MirrorEndpoint -> String
$cshowList :: [MirrorEndpoint] -> ShowS
showList :: [MirrorEndpoint] -> ShowS
Show)

-- | A private read endpoint with maintenance authority used only by Dredger.
data PrivateEndpoint = PrivateEndpoint
    { PrivateEndpoint -> Target
preTarget :: Target
    , PrivateEndpoint -> Maybe Secret
preToken :: Maybe Secret
    , PrivateEndpoint -> DeletionConsent
preConsent :: DeletionConsent
    }
    deriving stock (PrivateEndpoint -> PrivateEndpoint -> Bool
(PrivateEndpoint -> PrivateEndpoint -> Bool)
-> (PrivateEndpoint -> PrivateEndpoint -> Bool)
-> Eq PrivateEndpoint
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PrivateEndpoint -> PrivateEndpoint -> Bool
== :: PrivateEndpoint -> PrivateEndpoint -> Bool
$c/= :: PrivateEndpoint -> PrivateEndpoint -> Bool
/= :: PrivateEndpoint -> PrivateEndpoint -> Bool
Eq, Int -> PrivateEndpoint -> ShowS
[PrivateEndpoint] -> ShowS
PrivateEndpoint -> String
(Int -> PrivateEndpoint -> ShowS)
-> (PrivateEndpoint -> String)
-> ([PrivateEndpoint] -> ShowS)
-> Show PrivateEndpoint
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PrivateEndpoint -> ShowS
showsPrec :: Int -> PrivateEndpoint -> ShowS
$cshow :: PrivateEndpoint -> String
show :: PrivateEndpoint -> String
$cshowList :: [PrivateEndpoint] -> ShowS
showList :: [PrivateEndpoint] -> ShowS
Show)

-- | The mirror endpoint as the collision rules read it. The tag comes from 'meWrite'.
meTarget :: MirrorEndpoint -> Target
meTarget :: MirrorEndpoint -> Target
meTarget MirrorEndpoint
endpoint = StoreTag -> RegistryUrl -> Target
Target (MirrorWrite -> StoreTag
writeTag (MirrorEndpoint -> MirrorWrite
meWrite MirrorEndpoint
endpoint)) (MirrorEndpoint -> RegistryUrl
meUrl MirrorEndpoint
endpoint)
  where
    writeTag :: MirrorWrite -> StoreTag
writeTag = \case
        WriteRegistry{} -> StoreTag
TagRegistry
        WriteCodeArtifact{} -> StoreTag
TagCodeArtifact
        WriteVerdaccio{} -> StoreTag
TagVerdaccio

{- | A declared @publicationTarget@: where a client publish is relayed, and the static credential
forwarded __only when the publishing client sends none__.
-}
data PublicationEndpoint = PublicationEndpoint
    { PublicationEndpoint -> Target
peTarget :: Target
    , PublicationEndpoint -> Maybe Secret
peToken :: Maybe Secret
    }
    deriving stock (PublicationEndpoint -> PublicationEndpoint -> Bool
(PublicationEndpoint -> PublicationEndpoint -> Bool)
-> (PublicationEndpoint -> PublicationEndpoint -> Bool)
-> Eq PublicationEndpoint
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PublicationEndpoint -> PublicationEndpoint -> Bool
== :: PublicationEndpoint -> PublicationEndpoint -> Bool
$c/= :: PublicationEndpoint -> PublicationEndpoint -> Bool
/= :: PublicationEndpoint -> PublicationEndpoint -> Bool
Eq, Int -> PublicationEndpoint -> ShowS
[PublicationEndpoint] -> ShowS
PublicationEndpoint -> String
(Int -> PublicationEndpoint -> ShowS)
-> (PublicationEndpoint -> String)
-> ([PublicationEndpoint] -> ShowS)
-> Show PublicationEndpoint
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PublicationEndpoint -> ShowS
showsPrec :: Int -> PublicationEndpoint -> ShowS
$cshow :: PublicationEndpoint -> String
show :: PublicationEndpoint -> String
$cshowList :: [PublicationEndpoint] -> ShowS
showList :: [PublicationEndpoint] -> ShowS
Show)

-- | How a mount's mirror write authenticates, projected from its resolved 'StoreBackend'.
data MintPlan
    = -- | A CodeArtifact mirror target: the mint identity parsed from its host.
      MintCodeArtifact CodeArtifactConfig
    | -- | Any other mirror target: an operator-supplied static write token.
      MintStatic Secret
    deriving stock (MintPlan -> MintPlan -> Bool
(MintPlan -> MintPlan -> Bool)
-> (MintPlan -> MintPlan -> Bool) -> Eq MintPlan
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MintPlan -> MintPlan -> Bool
== :: MintPlan -> MintPlan -> Bool
$c/= :: MintPlan -> MintPlan -> Bool
/= :: MintPlan -> MintPlan -> Bool
Eq, Int -> MintPlan -> ShowS
[MintPlan] -> ShowS
MintPlan -> String
(Int -> MintPlan -> ShowS)
-> (MintPlan -> String) -> ([MintPlan] -> ShowS) -> Show MintPlan
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MintPlan -> ShowS
showsPrec :: Int -> MintPlan -> ShowS
$cshow :: MintPlan -> String
show :: MintPlan -> String
$cshowList :: [MintPlan] -> ShowS
showList :: [MintPlan] -> ShowS
Show)

-- | The control plane a mount's store offers, the face @ecluse dredger@ deletes through.
data ControlPlane
    = -- | The CodeArtifact repository the target addresses, which the load has vetted.
      ControlCodeArtifact CodeArtifactStore
    | {- | A store with no vendor control plane, swept through the ecosystem protocol's own
      verbs: its write token, and the operator's consent to delete from it.
      -}
      ControlProtocol Secret DeletionConsent
    | -- | The tag names no control plane this build implements.
      ControlNone
    deriving stock (ControlPlane -> ControlPlane -> Bool
(ControlPlane -> ControlPlane -> Bool)
-> (ControlPlane -> ControlPlane -> Bool) -> Eq ControlPlane
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ControlPlane -> ControlPlane -> Bool
== :: ControlPlane -> ControlPlane -> Bool
$c/= :: ControlPlane -> ControlPlane -> Bool
/= :: ControlPlane -> ControlPlane -> Bool
Eq, Int -> ControlPlane -> ShowS
[ControlPlane] -> ShowS
ControlPlane -> String
(Int -> ControlPlane -> ShowS)
-> (ControlPlane -> String)
-> ([ControlPlane] -> ShowS)
-> Show ControlPlane
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ControlPlane -> ShowS
showsPrec :: Int -> ControlPlane -> ShowS
$cshow :: ControlPlane -> String
show :: ControlPlane -> String
$cshowList :: [ControlPlane] -> ShowS
showList :: [ControlPlane] -> ShowS
Show)

{- | A mount's store backend, resolved once at load ("Ecluse.Config.Target"), so no two roles infer
a different one. The tag discriminates, so no arm pairs one store's mint with another's plane.
-}
data StoreBackend
    = -- | A protocol-speaking host: its static write token, and no control plane.
      BackendRegistry Secret
    | -- | A CodeArtifact repository: the identity it mints from, and the store a sweep deletes in.
      BackendCodeArtifact CodeArtifactConfig CodeArtifactStore
    | -- | A Verdaccio store: its static write token, and the operator's deletion consent.
      BackendVerdaccio Secret DeletionConsent
    deriving stock (StoreBackend -> StoreBackend -> Bool
(StoreBackend -> StoreBackend -> Bool)
-> (StoreBackend -> StoreBackend -> Bool) -> Eq StoreBackend
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: StoreBackend -> StoreBackend -> Bool
== :: StoreBackend -> StoreBackend -> Bool
$c/= :: StoreBackend -> StoreBackend -> Bool
/= :: StoreBackend -> StoreBackend -> Bool
Eq, Int -> StoreBackend -> ShowS
[StoreBackend] -> ShowS
StoreBackend -> String
(Int -> StoreBackend -> ShowS)
-> (StoreBackend -> String)
-> ([StoreBackend] -> ShowS)
-> Show StoreBackend
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> StoreBackend -> ShowS
showsPrec :: Int -> StoreBackend -> ShowS
$cshow :: StoreBackend -> String
show :: StoreBackend -> String
$cshowList :: [StoreBackend] -> ShowS
showList :: [StoreBackend] -> ShowS
Show)

-- | The tag a backend was declared under.
sbTag :: StoreBackend -> StoreTag
sbTag :: StoreBackend -> StoreTag
sbTag = \case
    BackendRegistry{} -> StoreTag
TagRegistry
    BackendCodeArtifact{} -> StoreTag
TagCodeArtifact
    BackendVerdaccio{} -> StoreTag
TagVerdaccio

-- | How the mirror write to this backend authenticates.
sbMint :: StoreBackend -> MintPlan
sbMint :: StoreBackend -> MintPlan
sbMint = \case
    BackendRegistry Secret
token -> Secret -> MintPlan
MintStatic Secret
token
    BackendCodeArtifact CodeArtifactConfig
caConfig CodeArtifactStore
_ -> CodeArtifactConfig -> MintPlan
MintCodeArtifact CodeArtifactConfig
caConfig
    BackendVerdaccio Secret
token DeletionConsent
_ -> Secret -> MintPlan
MintStatic Secret
token

-- | The control plane this build reaches for a backend.
sbControl :: StoreBackend -> ControlPlane
sbControl :: StoreBackend -> ControlPlane
sbControl = \case
    BackendRegistry{} -> ControlPlane
ControlNone
    BackendCodeArtifact CodeArtifactConfig
_ CodeArtifactStore
store -> CodeArtifactStore -> ControlPlane
ControlCodeArtifact CodeArtifactStore
store
    BackendVerdaccio Secret
token DeletionConsent
consent -> Secret -> DeletionConsent -> ControlPlane
ControlProtocol Secret
token DeletionConsent
consent

{- | The namespaces a mount's deployment owns, one arm per ecosystem, read only in that registry's
own naming shape. Every consumer of the privilege derives its predicate from this one value.
-}
data FirstParty
    = -- | The npm scopes the deployment owns, at least one.
      FirstPartyNpmScopes (NonEmpty Scope)
    | -- | The PyPI distributions and name prefixes the deployment owns, at least one.
      FirstPartyPyPI (NonEmpty PyPIFirstParty)
    deriving stock (FirstParty -> FirstParty -> Bool
(FirstParty -> FirstParty -> Bool)
-> (FirstParty -> FirstParty -> Bool) -> Eq FirstParty
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FirstParty -> FirstParty -> Bool
== :: FirstParty -> FirstParty -> Bool
$c/= :: FirstParty -> FirstParty -> Bool
/= :: FirstParty -> FirstParty -> Bool
Eq, Int -> FirstParty -> ShowS
[FirstParty] -> ShowS
FirstParty -> String
(Int -> FirstParty -> ShowS)
-> (FirstParty -> String)
-> ([FirstParty] -> ShowS)
-> Show FirstParty
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FirstParty -> ShowS
showsPrec :: Int -> FirstParty -> ShowS
$cshow :: FirstParty -> String
show :: FirstParty -> String
$cshowList :: [FirstParty] -> ShowS
showList :: [FirstParty] -> ShowS
Show)

{- | A mount's refinements of the global @integrity@ group, under its own @integrity@ key so the
mount groups them exactly as the top level does. Each is 'Nothing' at the global setting.
-}
newtype MountIntegrity = MountIntegrity
    { MountIntegrity -> Maybe MinTrustedIntegrity
miMinTrusted :: 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.
    -}
    }
    deriving stock (MountIntegrity -> MountIntegrity -> Bool
(MountIntegrity -> MountIntegrity -> Bool)
-> (MountIntegrity -> MountIntegrity -> Bool) -> Eq MountIntegrity
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MountIntegrity -> MountIntegrity -> Bool
== :: MountIntegrity -> MountIntegrity -> Bool
$c/= :: MountIntegrity -> MountIntegrity -> Bool
/= :: MountIntegrity -> MountIntegrity -> Bool
Eq, Int -> MountIntegrity -> ShowS
[MountIntegrity] -> ShowS
MountIntegrity -> String
(Int -> MountIntegrity -> ShowS)
-> (MountIntegrity -> String)
-> ([MountIntegrity] -> ShowS)
-> Show MountIntegrity
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MountIntegrity -> ShowS
showsPrec :: Int -> MountIntegrity -> ShowS
$cshow :: MountIntegrity -> String
show :: MountIntegrity -> String
$cshowList :: [MountIntegrity] -> ShowS
showList :: [MountIntegrity] -> ShowS
Show)

-- | One mount as the document declares it, before "Ecluse.Config" resolves it into a 'Mount'.
data MountConfig = MountConfig
    { MountConfig -> Maybe Bool
mntEnabled :: Maybe Bool
    {- ^ The mount's on\/off switch. Any operator-declared key already activates the mount, so
    @true@ serves the public gate that declares no other key and @false@ switches one off in place.
    -}
    , MountConfig -> Maybe PrivateEndpoint
mntPrivateUpstream :: Maybe PrivateEndpoint
    , MountConfig -> RegistryUrl
mntPublicUpstream :: RegistryUrl
    -- ^ Only the @registry@ tag is admitted here, so the URL is the whole declaration.
    , MountConfig -> Maybe MirrorEndpoint
mntMirrorTarget :: Maybe MirrorEndpoint
    , MountConfig -> Maybe PublicationEndpoint
mntPublicationTarget :: Maybe PublicationEndpoint
    , MountConfig -> Maybe FirstParty
mntFirstParty :: Maybe FirstParty
    , MountConfig -> MountIntegrity
mntIntegrity :: MountIntegrity
    , 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. 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 -> DredgerSettings
cfgDredger :: DredgerSettings
    , 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, and 'Ecluse.Config.loadConfig' refuses
    otherwise. The proxy rewrites served artifact URLs 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, depth cap, and redelivery budget. The
URL's shape decides the backend, and the load derives it once ("Ecluse.Config.QueueTarget").
-}
data QueueSettings = QueueSettings
    { QueueSettings -> Maybe QueueUrl
qsUrl :: Maybe QueueUrl
    , QueueSettings -> Maybe Int
qsMaxMemoryDepth :: Maybe Int
    -- ^ Computed from the runtime posture when unset. A configured value wins.
    , QueueSettings -> Int
qsMaxReceiveCount :: Int
    {- ^ Deliveries one message gets before the worker retires it. A __floor__: a queue with a
    dead-letter terminus runs one above its capture count, so it captures first ("Ecluse.Core.Queue").
    -}
    }
    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 memory plan computes the tenant-sized caps
when unset ("Ecluse.Composition.MemoryPlan"), a configured value winning, and the rest stay pinned.
-}
data LimitsSettings = LimitsSettings
    { LimitsSettings -> Maybe Int
limMaxResponseBytes :: Maybe Int
    , LimitsSettings -> Int
limMaxVersionCount :: Int
    , LimitsSettings -> Int
limMaxArtifactCount :: Int
    {- ^ The per-package artifact fan-out cap. It bounds document shape rather than bytes, so
    the memory plan does not size it and it stays pinned.
    -}
    , LimitsSettings -> Int
limMaxNestingDepth :: Int
    , LimitsSettings -> Int
limMaxAdvisoryDatabaseBytes :: Int
    {- ^ The advisory-database download cap. It bounds a stream to disk rather than a heap
    tenant, so the memory plan does not size it and it stays pinned.
    -}
    , 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.
    -}
    , LimitsSettings -> Int
limProgressWindow :: Int
    -- ^ Seconds of waiting within which an upstream body must deliver 'limMinProgressBytes'.
    , LimitsSettings -> Int
limMinProgressBytes :: Int
    -- ^ Body bytes an upstream exchange must receive within each progress window.
    }
    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)

-- | Global integrity floors. A mount can refine the trusted floor through 'MountIntegrity'.
data IntegritySettings = IntegritySettings
    { IntegritySettings -> MinIntegrity
intMinPublic :: MinIntegrity
    , IntegritySettings -> MinTrustedIntegrity
intMinTrusted :: MinTrustedIntegrity
    }
    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 store, cadences, and upstream feeds.
data AdvisoriesSettings = AdvisoriesSettings
    { AdvisoriesSettings -> Maybe AdvisoryStoreUrl
advUrl :: Maybe AdvisoryStoreUrl
    {- ^ The object store the compiled databases sync from, its provider derived from the URL's
    scheme ("Ecluse.Config.AdvisoryStore"). 'Nothing' leaves the advisory stack off.
    -}
    , AdvisoriesSettings -> NominalDiffTime
advPollInterval :: NominalDiffTime
    , AdvisoriesSettings -> NominalDiffTime
advCompileInterval :: NominalDiffTime
    , AdvisoriesSettings -> String
advDataDir :: FilePath
    , AdvisoriesSettings -> Url
advOsvExportBaseUrl :: Url
    , AdvisoriesSettings -> Url
advEpssFeedUrl :: Url
    , AdvisoriesSettings -> Map Ecosystem NominalDiffTime
advQuietTime :: Map Ecosystem NominalDiffTime
    {- ^ Pilot only: how long one ecosystem's advisory export may go unchanged before Pilot
    logs the quiet-time alarm. An ecosystem with no entry takes the shipped seven days.
    -}
    , AdvisoriesSettings -> NominalDiffTime
advEpssQuietTime :: NominalDiffTime
    -- ^ Pilot only: the same threshold for the EPSS feed's declared score date.
    , AdvisoriesSettings -> Maybe NominalDiffTime
advMaxAgeSeconds :: Maybe NominalDiffTime
    {- ^ How old a published advisory artifact may be before CVE-based denial refuses. Unset,
    each mount derives its own from its quarantine rules.
    -}
    }
    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. 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
rtCoresCeiling :: 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, log level, and telemetry switch.
data ObservabilitySettings = ObservabilitySettings
    { ObservabilitySettings -> LogFormat
obsLogFormat :: LogFormat
    , ObservabilitySettings -> LogLevel
obsLogLevel :: LogLevel
    , 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)

{- | The @dredger@ group: how the mirror sweep paces itself, how much one cycle may delete, and
which names it carries. Only @ecluse dredger@ reads it, and every other role carries it unread.
-}
data DredgerSettings = DredgerSettings
    { DredgerSettings -> Int
drgChunkSize :: Int
    -- ^ Candidate packages one chunk examines before the sweep pauses.
    , DredgerSettings -> NominalDiffTime
drgChunkPause :: NominalDiffTime
    -- ^ Seconds between chunks, which is also the wait a fault advising no delay of its own takes.
    , DredgerSettings -> NominalDiffTime
drgCyclePause :: NominalDiffTime
    -- ^ Seconds between the end of one cycle and the start of the next.
    , DredgerSettings -> Maybe NominalDiffTime
drgTargetCycleWindow :: Maybe NominalDiffTime
    {- ^ Seconds within which an advisory is paced to reach every affected mirrored version.
    Computed from the cycle pause when unset.
    -}
    , DredgerSettings -> Maybe Rational
drgRequestBudgetFraction :: Maybe Rational
    {- ^ The share of a store's request capacity one sweep may take. Computed per capacity pool
    when unset, so a sweep cannot starve the proxy's own calls.
    -}
    , DredgerSettings -> Map Text QuotaOverride
drgQuotaOverrides :: Map Text QuotaOverride
    -- ^ Declared request capacity, keyed by the store URL it describes.
    , DredgerSettings -> Maybe Int
drgDeletionCap :: Maybe Int
    {- ^ Versions one cycle may hand over for deletion, computed per sweepable store when unset.
    Reaching it halts the sweep for the life of the process, so a poisoned generation stops there.
    -}
    , DredgerSettings -> Bool
drgFullWalk :: Bool
    {- ^ Walk every package each cycle rather than the advisory and identity-deny candidates. It
    covers a rule-configuration change, and it writes one resumption marker to the store.
    -}
    }
    deriving stock (DredgerSettings -> DredgerSettings -> Bool
(DredgerSettings -> DredgerSettings -> Bool)
-> (DredgerSettings -> DredgerSettings -> Bool)
-> Eq DredgerSettings
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: DredgerSettings -> DredgerSettings -> Bool
== :: DredgerSettings -> DredgerSettings -> Bool
$c/= :: DredgerSettings -> DredgerSettings -> Bool
/= :: DredgerSettings -> DredgerSettings -> Bool
Eq, Int -> DredgerSettings -> ShowS
[DredgerSettings] -> ShowS
DredgerSettings -> String
(Int -> DredgerSettings -> ShowS)
-> (DredgerSettings -> String)
-> ([DredgerSettings] -> ShowS)
-> Show DredgerSettings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> DredgerSettings -> ShowS
showsPrec :: Int -> DredgerSettings -> ShowS
$cshow :: DredgerSettings -> String
show :: DredgerSettings -> String
$cshowList :: [DredgerSettings] -> ShowS
showList :: [DredgerSettings] -> ShowS
Show)

{- | One store's declared request capacity. It supplies the capacity a backend publishes none of,
and a shared scope joins two endpoints of one pool so the sweep paces them together.
-}
data QuotaOverride = QuotaOverride
    { QuotaOverride -> Maybe Text
qoScope :: Maybe Text
    -- ^ The capacity pool this store shares, which defaults to the store's own authority.
    , QuotaOverride -> Map QuotaDimension Rational
qoQuotas :: Map QuotaDimension Rational
    -- ^ Requests per second the pool admits, replacing the backend's own number per dimension.
    , QuotaOverride -> Map RequestKind Rational
qoWeights :: Map RequestKind Rational
    -- ^ What one request of a kind costs relative to the backend's own cost for it.
    }
    deriving stock (QuotaOverride -> QuotaOverride -> Bool
(QuotaOverride -> QuotaOverride -> Bool)
-> (QuotaOverride -> QuotaOverride -> Bool) -> Eq QuotaOverride
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: QuotaOverride -> QuotaOverride -> Bool
== :: QuotaOverride -> QuotaOverride -> Bool
$c/= :: QuotaOverride -> QuotaOverride -> Bool
/= :: QuotaOverride -> QuotaOverride -> Bool
Eq, Int -> QuotaOverride -> ShowS
[QuotaOverride] -> ShowS
QuotaOverride -> String
(Int -> QuotaOverride -> ShowS)
-> (QuotaOverride -> String)
-> ([QuotaOverride] -> ShowS)
-> Show QuotaOverride
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> QuotaOverride -> ShowS
showsPrec :: Int -> QuotaOverride -> ShowS
$cshow :: QuotaOverride -> String
show :: QuotaOverride -> String
$cshowList :: [QuotaOverride] -> ShowS
showList :: [QuotaOverride] -> ShowS
Show)

-- | A resolved mount's endpoints: the public upstream it gates, and what it does with the rest.
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
it 'Mirrored', which carries the private upstream the mirror is read back through, never without.
-}
data MountMode
    = -- | The mount mirrors admitted public artifacts, and it needs both legs.
      Mirrored MirroredLegs
    | -- | The mount never writes. It still merges the optional private upstream 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. It is total over both mount modes.
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

-- | A mirror target married to the backend resolved from the tag it was declared under.
data MirrorTarget = MirrorTarget
    { MirrorTarget -> RegistryUrl
mtUrl :: RegistryUrl
    , MirrorTarget -> StoreBackend
mtBackend :: StoreBackend
    }
    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)

-- | One resolved mount: its ecosystem, its endpoints, and the rules in precedence order.
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)

-- | The mounts a load resolved, keyed by the ecosystem each was declared under.
type MountMap = Map Ecosystem Mount

-- | A completed load: the document's settings and the mounts resolved against them.
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)

-- | Why a load was refused. 'renderConfigError' writes each one as the boot reports it.
data ConfigError
    = ParseError Text
    | PolicyErrors [PolicyError]
    | {- | A mount is active but @server.publicUrl@ is unset. It is not derived from the @Host@
      header: a spoofed header poisons shared-cache entries with an attacker-chosen artifact URL.
      -}
      PublicUrlRequired
    | {- | A mount declares a @mirrorTarget@ but no private upstream, through which the mirror
      write must be readable back. A serve-only mount never raises this.
      -}
      MountMissingPrivateUpstream Ecosystem
    | {- | An endpoint declared under the @codeArtifact@ tag whose URL is not a CodeArtifact
      endpoint. The tag names the store, so a URL contradicting it is a misdirected write or read.
      -}
      CodeArtifactHostMismatch Ecosystem Text
    | {- | A @codeArtifact@ endpoint on a mount whose ecosystem CodeArtifact carries no package
      format for, so no repository under it could serve the mount. Carries the key it was written at.
      -}
      CodeArtifactFormatUnsupported Ecosystem Text
    | {- | A @codeArtifact@ endpoint whose path addresses no repository under the mount's own
      format, whose per-format endpoints are separate stores. Carries its key, then the format token.
      -}
      CodeArtifactRepositoryMissing Ecosystem Text Text
    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) =
    Text
"mount \""
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text
ecosystemName Ecosystem
eco
        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 "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text -> Text
keyRef Ecosystem
eco Text
"privateUpstream"
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" in the config document, or remove "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text -> Text
mountDocRef Ecosystem
eco Text
"mirrorTarget"
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" for a serve-only mount that never mirrors"
renderConfigError (CodeArtifactHostMismatch Ecosystem
eco Text
path) =
    Ecosystem -> Text -> Text
keyRef Ecosystem
eco Text
path
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" is declared under the codeArtifact tag, but its host is not a CodeArtifact endpoint "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"({domain}-{account}.d.codeartifact.{region}.amazonaws.com): correct the URL, or declare this "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"endpoint under the tag that names its store"
renderConfigError (CodeArtifactFormatUnsupported Ecosystem
eco Text
path) =
    Ecosystem -> Text -> Text
keyRef Ecosystem
eco Text
path
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" names a CodeArtifact store, but CodeArtifact carries no package format for the "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text
ecosystemName Ecosystem
eco
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ecosystem: point this endpoint at a store CodeArtifact serves"
renderConfigError (CodeArtifactRepositoryMissing Ecosystem
eco Text
path Text
format) =
    Ecosystem -> Text -> Text
keyRef Ecosystem
eco Text
path
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" is not a CodeArtifact repository endpoint for this mount: its path must be /"
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
format
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/{repository}/"

-- A mount-scoped key in both spellings, the form every refusal above names it in.
keyRef :: Ecosystem -> Text -> Text
keyRef :: Ecosystem -> Text -> Text
keyRef Ecosystem
eco Text
path = Ecosystem -> Text -> Text
mountDocRef Ecosystem
eco Text
path Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text -> Text
mountKeyRef Ecosystem
eco Text
path Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"