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

{- | What one sweep cycle did, why it stopped early if it did, and what it could not read.

A count is only as good as the evidence behind it, so a cycle reports its halt and its gaps
beside its tally rather than folding them in. The rendered lines are the operator's, so their
wording is the reported behaviour.
-}
module Ecluse.Core.Registry.Sweep.Outcome (
    -- * What one cycle did
    SweepTally (..),
    CycleHalt (..),
    CycleOutcome (..),
    outcomeComplete,
    latches,
    renderCycleHalt,
    renderGeneration,
    renderTally,
    renderStoreFault,
    storeSubject,

    -- * What a preview found standing in a real sweep's way
    TargetPrerequisites (..),
    PrerequisiteStatus (..),
    prerequisitesMet,
    renderPrerequisites,

    -- * What a cycle could not read
    EvidenceGaps (..),
    unloadedGeneration,
    unreadManifest,
    evidenceComplete,
    renderEvidenceGaps,
) where

import Data.Text qualified as T

import Ecluse.Core.Cve.Types (DbEtag (DbEtag))
import Ecluse.Core.Ecosystem (Ecosystem, ecosystemName)
import Ecluse.Core.Fault (renderTransportCause, tfCause, tfDetail)
import Ecluse.Core.Registry.Maintenance (StoreFault (faultTransport))

-- | What one cycle did with the versions it examined.
data SweepTally = SweepTally
    { SweepTally -> Int
tallyExamined :: Int
    , SweepTally -> Int
tallyDeleted :: Int
    , SweepTally -> Int
tallyKept :: Int
    , SweepTally -> Int
tallyGuardSkipped :: Int
    }
    deriving stock (SweepTally -> SweepTally -> Bool
(SweepTally -> SweepTally -> Bool)
-> (SweepTally -> SweepTally -> Bool) -> Eq SweepTally
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SweepTally -> SweepTally -> Bool
== :: SweepTally -> SweepTally -> Bool
$c/= :: SweepTally -> SweepTally -> Bool
/= :: SweepTally -> SweepTally -> Bool
Eq, Int -> SweepTally -> ShowS
[SweepTally] -> ShowS
SweepTally -> String
(Int -> SweepTally -> ShowS)
-> (SweepTally -> String)
-> ([SweepTally] -> ShowS)
-> Show SweepTally
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SweepTally -> ShowS
showsPrec :: Int -> SweepTally -> ShowS
$cshow :: SweepTally -> String
show :: SweepTally -> String
$cshowList :: [SweepTally] -> ShowS
showList :: [SweepTally] -> ShowS
Show)

instance Semigroup SweepTally where
    SweepTally
left <> :: SweepTally -> SweepTally -> SweepTally
<> SweepTally
right =
        SweepTally
            { tallyExamined :: Int
tallyExamined = SweepTally -> Int
tallyExamined SweepTally
left Int -> Int -> Int
forall a. Num a => a -> a -> a
+ SweepTally -> Int
tallyExamined SweepTally
right
            , tallyDeleted :: Int
tallyDeleted = SweepTally -> Int
tallyDeleted SweepTally
left Int -> Int -> Int
forall a. Num a => a -> a -> a
+ SweepTally -> Int
tallyDeleted SweepTally
right
            , tallyKept :: Int
tallyKept = SweepTally -> Int
tallyKept SweepTally
left Int -> Int -> Int
forall a. Num a => a -> a -> a
+ SweepTally -> Int
tallyKept SweepTally
right
            , tallyGuardSkipped :: Int
tallyGuardSkipped = SweepTally -> Int
tallyGuardSkipped SweepTally
left Int -> Int -> Int
forall a. Num a => a -> a -> a
+ SweepTally -> Int
tallyGuardSkipped SweepTally
right
            }

instance Monoid SweepTally where
    mempty :: SweepTally
mempty = Int -> Int -> Int -> Int -> SweepTally
SweepTally Int
0 Int
0 Int
0 Int
0

-- | Why a cycle stopped before it finished.
data CycleHalt
    = -- | The store carries no consent marker, with the backend and its how-to-attach text.
      HaltConsentWithheld Ecosystem Text Text
    | -- | The store refills itself from elsewhere, so deleting from it changes nothing.
      HaltStorePreserved Ecosystem Text Text
    | {- | The cycle reached its deletion cap, carrying the cap, what it handed over, and the
      advisory generation of the denial that reached it. No later cycle runs.
      -}
      HaltDeletionCap Int Int (Maybe DbEtag)
    | -- | A store call produced no answer and its retry advice ran out, carrying the fault.
      HaltStoreFault Ecosystem Text Text
    | {- | A bucket outgrew the memory budget and nothing narrows it further, so the walk cannot
      read it within the budget.
      -}
      HaltBucketUnsplittable Ecosystem Text Text
    deriving stock (CycleHalt -> CycleHalt -> Bool
(CycleHalt -> CycleHalt -> Bool)
-> (CycleHalt -> CycleHalt -> Bool) -> Eq CycleHalt
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CycleHalt -> CycleHalt -> Bool
== :: CycleHalt -> CycleHalt -> Bool
$c/= :: CycleHalt -> CycleHalt -> Bool
/= :: CycleHalt -> CycleHalt -> Bool
Eq, Int -> CycleHalt -> ShowS
[CycleHalt] -> ShowS
CycleHalt -> String
(Int -> CycleHalt -> ShowS)
-> (CycleHalt -> String)
-> ([CycleHalt] -> ShowS)
-> Show CycleHalt
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CycleHalt -> ShowS
showsPrec :: Int -> CycleHalt -> ShowS
$cshow :: CycleHalt -> String
show :: CycleHalt -> String
$cshowList :: [CycleHalt] -> ShowS
showList :: [CycleHalt] -> ShowS
Show)

-- | One cycle's result: what it did, why it stopped early if it did, and what it could not read.
data CycleOutcome = CycleOutcome
    { CycleOutcome -> Maybe CycleHalt
outcomeHalt :: Maybe CycleHalt
    , CycleOutcome -> SweepTally
outcomeTally :: SweepTally
    , CycleOutcome -> [TargetPrerequisites]
outcomePrerequisites :: [TargetPrerequisites]
    {- ^ What a preview found of each target's standing permissions, in mount order. A run that
    refuses on them instead reports none.
    -}
    , CycleOutcome -> EvidenceGaps
outcomeEvidence :: EvidenceGaps
    -- ^ What the cycle could not read, which is separate from whether a real sweep may delete.
    }
    deriving stock (CycleOutcome -> CycleOutcome -> Bool
(CycleOutcome -> CycleOutcome -> Bool)
-> (CycleOutcome -> CycleOutcome -> Bool) -> Eq CycleOutcome
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CycleOutcome -> CycleOutcome -> Bool
== :: CycleOutcome -> CycleOutcome -> Bool
$c/= :: CycleOutcome -> CycleOutcome -> Bool
/= :: CycleOutcome -> CycleOutcome -> Bool
Eq, Int -> CycleOutcome -> ShowS
[CycleOutcome] -> ShowS
CycleOutcome -> String
(Int -> CycleOutcome -> ShowS)
-> (CycleOutcome -> String)
-> ([CycleOutcome] -> ShowS)
-> Show CycleOutcome
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CycleOutcome -> ShowS
showsPrec :: Int -> CycleOutcome -> ShowS
$cshow :: CycleOutcome -> String
show :: CycleOutcome -> String
$cshowList :: [CycleOutcome] -> ShowS
showList :: [CycleOutcome] -> ShowS
Show)

{- | Whether a cycle's counts cover what they claim to: it walked the whole store it was given,
and every rule that decided read the facts it needed. Nothing about permission enters here.
-}
outcomeComplete :: CycleOutcome -> Bool
outcomeComplete :: CycleOutcome -> Bool
outcomeComplete CycleOutcome
outcome =
    Maybe CycleHalt -> Bool
forall a. Maybe a -> Bool
isNothing (CycleOutcome -> Maybe CycleHalt
outcomeHalt CycleOutcome
outcome) Bool -> Bool -> Bool
&& EvidenceGaps -> Bool
evidenceComplete (CycleOutcome -> EvidenceGaps
outcomeEvidence CycleOutcome
outcome)

{- | Whether a halt stops the Dredger for the life of the process. Only the cap does, because a
breaker that re-closes itself is not a breaker. Every other halt is re-read next cycle.
-}
latches :: CycleHalt -> Bool
latches :: CycleHalt -> Bool
latches = \case
    HaltDeletionCap{} -> Bool
True
    HaltConsentWithheld{} -> Bool
False
    HaltStorePreserved{} -> Bool
False
    HaltStoreFault{} -> Bool
False
    HaltBucketUnsplittable{} -> Bool
False

-- | The operator-facing text of a halt, naming the backend that raised it and what to fix.
renderCycleHalt :: CycleHalt -> Text
renderCycleHalt :: CycleHalt -> Text
renderCycleHalt = \case
    HaltConsentWithheld Ecosystem
eco Text
backend Text
descriptor ->
        Ecosystem -> Text -> Text
storeSubject Ecosystem
eco Text
backend Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" carries no deletion consent marker: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
descriptor
    HaltStorePreserved Ecosystem
eco Text
backend Text
why ->
        Ecosystem -> Text -> Text
storeSubject Ecosystem
eco Text
backend Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" refills itself, so a delete changes nothing: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
why
    HaltDeletionCap Int
cap Int
issued Maybe DbEtag
etag ->
        Text
"the cycle handed over "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
issued
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" versions and reached its deletion cap of "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
cap
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" under advisory generation "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Maybe DbEtag -> Text
renderGeneration Maybe DbEtag
etag
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", so the Dredger runs no further cycle until it is restarted deliberately"
    HaltStoreFault Ecosystem
eco Text
backend Text
fault ->
        Text
"a call against " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text -> Text
storeSubject Ecosystem
eco Text
backend Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" produced no answer: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
fault
    HaltBucketUnsplittable Ecosystem
eco Text
backend Text
bucket ->
        Text
"the walk over "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text -> Text
storeSubject Ecosystem
eco Text
backend
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" cannot read the bucket of names beginning \""
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
bucket
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\": it holds more names than one bucket may, and no narrower bucket divides them"

-- | The advisory generation an audit line names, or that none was loaded.
renderGeneration :: Maybe DbEtag -> Text
renderGeneration :: Maybe DbEtag -> Text
renderGeneration = Text -> (DbEtag -> Text) -> Maybe DbEtag -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"none" (\(DbEtag Text
etag) -> Text
etag)

-- | One cycle's counts, as its closing line reports them.
renderTally :: SweepTally -> Text
renderTally :: SweepTally -> Text
renderTally SweepTally
tally =
    Text
"examined "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show (SweepTally -> Int
tallyExamined SweepTally
tally)
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", deleted "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show (SweepTally -> Int
tallyDeleted SweepTally
tally)
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", kept "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show (SweepTally -> Int
tallyKept SweepTally
tally)
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", guard-skipped "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show (SweepTally -> Int
tallyGuardSkipped SweepTally
tally)

-- | A store fault as an operator reads it: the transport's own cause and its bounded detail.
renderStoreFault :: StoreFault -> Text
renderStoreFault :: StoreFault -> Text
renderStoreFault StoreFault
fault = TransportCause -> Text
renderTransportCause (TransportFault -> TransportCause
tfCause TransportFault
transport) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> TransportFault -> Text
tfDetail TransportFault
transport
  where
    transport :: TransportFault
transport = StoreFault -> TransportFault
faultTransport StoreFault
fault

-- | Name a store without assuming whether it is the mirror or private target.
storeSubject :: Ecosystem -> Text -> Text
storeSubject :: Ecosystem -> Text -> Text
storeSubject Ecosystem
eco Text
backend = Text
"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
" store on " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
backend

-- | One target's standing permissions as a preview found them.
data TargetPrerequisites = TargetPrerequisites
    { TargetPrerequisites -> Ecosystem
tpEcosystem :: Ecosystem
    , TargetPrerequisites -> Text
tpBackend :: Text
    , TargetPrerequisites -> PrerequisiteStatus
tpConsent :: PrerequisiteStatus
    -- ^ Whether the store carries the operator's own deletion consent marker.
    , TargetPrerequisites -> PrerequisiteStatus
tpClassification :: PrerequisiteStatus
    -- ^ Whether deleting from the store destroys anything.
    }
    deriving stock (TargetPrerequisites -> TargetPrerequisites -> Bool
(TargetPrerequisites -> TargetPrerequisites -> Bool)
-> (TargetPrerequisites -> TargetPrerequisites -> Bool)
-> Eq TargetPrerequisites
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TargetPrerequisites -> TargetPrerequisites -> Bool
== :: TargetPrerequisites -> TargetPrerequisites -> Bool
$c/= :: TargetPrerequisites -> TargetPrerequisites -> Bool
/= :: TargetPrerequisites -> TargetPrerequisites -> Bool
Eq, Int -> TargetPrerequisites -> ShowS
[TargetPrerequisites] -> ShowS
TargetPrerequisites -> String
(Int -> TargetPrerequisites -> ShowS)
-> (TargetPrerequisites -> String)
-> ([TargetPrerequisites] -> ShowS)
-> Show TargetPrerequisites
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TargetPrerequisites -> ShowS
showsPrec :: Int -> TargetPrerequisites -> ShowS
$cshow :: TargetPrerequisites -> String
show :: TargetPrerequisites -> String
$cshowList :: [TargetPrerequisites] -> ShowS
showList :: [TargetPrerequisites] -> ShowS
Show)

{- | What a preview could see of one standing permission. A preview exercises none of them, so an
unmet one is reported and never acted on.
-}
data PrerequisiteStatus
    = -- | The store answered, and a real sweep would pass this one.
      PrerequisiteMet
    | -- | The store answered, and a real sweep would stop here, carrying the backend's own text.
      PrerequisiteUnmet Text
    | -- | The store did not answer, so nothing the preview read settles it.
      PrerequisiteUnread Text
    deriving stock (PrerequisiteStatus -> PrerequisiteStatus -> Bool
(PrerequisiteStatus -> PrerequisiteStatus -> Bool)
-> (PrerequisiteStatus -> PrerequisiteStatus -> Bool)
-> Eq PrerequisiteStatus
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PrerequisiteStatus -> PrerequisiteStatus -> Bool
== :: PrerequisiteStatus -> PrerequisiteStatus -> Bool
$c/= :: PrerequisiteStatus -> PrerequisiteStatus -> Bool
/= :: PrerequisiteStatus -> PrerequisiteStatus -> Bool
Eq, Int -> PrerequisiteStatus -> ShowS
[PrerequisiteStatus] -> ShowS
PrerequisiteStatus -> String
(Int -> PrerequisiteStatus -> ShowS)
-> (PrerequisiteStatus -> String)
-> ([PrerequisiteStatus] -> ShowS)
-> Show PrerequisiteStatus
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PrerequisiteStatus -> ShowS
showsPrec :: Int -> PrerequisiteStatus -> ShowS
$cshow :: PrerequisiteStatus -> String
show :: PrerequisiteStatus -> String
$cshowList :: [PrerequisiteStatus] -> ShowS
showList :: [PrerequisiteStatus] -> ShowS
Show)

-- | Whether a real sweep of this target would pass both standing permissions.
prerequisitesMet :: TargetPrerequisites -> Bool
prerequisitesMet :: TargetPrerequisites -> Bool
prerequisitesMet TargetPrerequisites
target = (PrerequisiteStatus -> Bool) -> [PrerequisiteStatus] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (PrerequisiteStatus -> PrerequisiteStatus -> Bool
forall a. Eq a => a -> a -> Bool
== PrerequisiteStatus
PrerequisiteMet) [TargetPrerequisites -> PrerequisiteStatus
tpConsent TargetPrerequisites
target, TargetPrerequisites -> PrerequisiteStatus
tpClassification TargetPrerequisites
target]

{- | One target's line, which a preview prints above its counts. It closes on what no read
settles: a preview deletes nothing, so it proves no authority to delete.
-}
renderPrerequisites :: TargetPrerequisites -> Text
renderPrerequisites :: TargetPrerequisites -> Text
renderPrerequisites TargetPrerequisites
target =
    Ecosystem -> Text -> Text
storeSubject (TargetPrerequisites -> Ecosystem
tpEcosystem TargetPrerequisites
target) (TargetPrerequisites -> Text
tpBackend TargetPrerequisites
target)
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": deletion consent "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PrerequisiteStatus -> Text
renderPrerequisite (TargetPrerequisites -> PrerequisiteStatus
tpConsent TargetPrerequisites
target)
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", and store classification "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PrerequisiteStatus -> Text
renderPrerequisite (TargetPrerequisites -> PrerequisiteStatus
tpClassification TargetPrerequisites
target)
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
". This preview deleted nothing, so it proves no authority to delete"

renderPrerequisite :: PrerequisiteStatus -> Text
renderPrerequisite :: PrerequisiteStatus -> Text
renderPrerequisite = \case
    PrerequisiteStatus
PrerequisiteMet -> Text
"is met"
    PrerequisiteUnmet Text
detail -> Text
"is not met: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
detail
    PrerequisiteUnread Text
detail -> Text
"could not be read: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
detail

{- | What one cycle could not read. A count taken with a gap open describes part of the store, so
it is reported apart from the counts themselves rather than folded into them.
-}
data EvidenceGaps = EvidenceGaps
    { EvidenceGaps -> Int
gapAdvisoryGeneration :: Int
    -- ^ Mounts that decided with no advisory generation loaded, so every advisory rule abstained.
    , EvidenceGaps -> Int
gapManifests :: Int
    -- ^ Packages whose metadata the store did not serve, decided on identity alone.
    }
    deriving stock (EvidenceGaps -> EvidenceGaps -> Bool
(EvidenceGaps -> EvidenceGaps -> Bool)
-> (EvidenceGaps -> EvidenceGaps -> Bool) -> Eq EvidenceGaps
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: EvidenceGaps -> EvidenceGaps -> Bool
== :: EvidenceGaps -> EvidenceGaps -> Bool
$c/= :: EvidenceGaps -> EvidenceGaps -> Bool
/= :: EvidenceGaps -> EvidenceGaps -> Bool
Eq, Int -> EvidenceGaps -> ShowS
[EvidenceGaps] -> ShowS
EvidenceGaps -> String
(Int -> EvidenceGaps -> ShowS)
-> (EvidenceGaps -> String)
-> ([EvidenceGaps] -> ShowS)
-> Show EvidenceGaps
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> EvidenceGaps -> ShowS
showsPrec :: Int -> EvidenceGaps -> ShowS
$cshow :: EvidenceGaps -> String
show :: EvidenceGaps -> String
$cshowList :: [EvidenceGaps] -> ShowS
showList :: [EvidenceGaps] -> ShowS
Show)

instance Semigroup EvidenceGaps where
    EvidenceGaps
left <> :: EvidenceGaps -> EvidenceGaps -> EvidenceGaps
<> EvidenceGaps
right =
        EvidenceGaps
            { gapAdvisoryGeneration :: Int
gapAdvisoryGeneration = EvidenceGaps -> Int
gapAdvisoryGeneration EvidenceGaps
left Int -> Int -> Int
forall a. Num a => a -> a -> a
+ EvidenceGaps -> Int
gapAdvisoryGeneration EvidenceGaps
right
            , gapManifests :: Int
gapManifests = EvidenceGaps -> Int
gapManifests EvidenceGaps
left Int -> Int -> Int
forall a. Num a => a -> a -> a
+ EvidenceGaps -> Int
gapManifests EvidenceGaps
right
            }

instance Monoid EvidenceGaps where
    mempty :: EvidenceGaps
mempty = Int -> Int -> EvidenceGaps
EvidenceGaps Int
0 Int
0

-- | The gap one mount deciding without an advisory generation leaves.
unloadedGeneration :: EvidenceGaps
unloadedGeneration :: EvidenceGaps
unloadedGeneration = EvidenceGaps
forall a. Monoid a => a
mempty{gapAdvisoryGeneration = 1}

-- | The gap one package the store served no metadata for leaves.
unreadManifest :: EvidenceGaps
unreadManifest :: EvidenceGaps
unreadManifest = EvidenceGaps
forall a. Monoid a => a
mempty{gapManifests = 1}

-- | Whether a cycle read every fact its counts rest on.
evidenceComplete :: EvidenceGaps -> Bool
evidenceComplete :: EvidenceGaps -> Bool
evidenceComplete EvidenceGaps
gaps = EvidenceGaps
gaps EvidenceGaps -> EvidenceGaps -> Bool
forall a. Eq a => a -> a -> Bool
== EvidenceGaps
forall a. Monoid a => a
mempty

-- | What a cycle could not read, as its closing line reports it, naming only what it did miss.
renderEvidenceGaps :: EvidenceGaps -> Text
renderEvidenceGaps :: EvidenceGaps -> Text
renderEvidenceGaps EvidenceGaps
gaps =
    Text -> [Text] -> Text
T.intercalate
        Text
", "
        ( [Maybe Text] -> [Text]
forall a. [Maybe a] -> [a]
catMaybes
            [ Int -> Text -> Text -> Maybe Text
forall {a} {a}.
(Ord a, Num a, Semigroup a, IsString a, Show a) =>
a -> a -> a -> Maybe a
counted (EvidenceGaps -> Int
gapAdvisoryGeneration EvidenceGaps
gaps) Text
"mount" Text
"decided without an advisory generation"
            , Int -> Text -> Text -> Maybe Text
forall {a} {a}.
(Ord a, Num a, Semigroup a, IsString a, Show a) =>
a -> a -> a -> Maybe a
counted (EvidenceGaps -> Int
gapManifests EvidenceGaps
gaps) Text
"package" Text
"decided without the store's own metadata"
            ]
        )
  where
    counted :: a -> a -> a -> Maybe a
counted a
count a
noun a
what
        | a
count a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= a
0 = Maybe a
forall a. Maybe a
Nothing
        | a
count a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
1 = a -> Maybe a
forall a. a -> Maybe a
Just (a
"1 " a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
noun a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
" " a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
what)
        | Bool
otherwise = a -> Maybe a
forall a. a -> Maybe a
Just (a -> a
forall b a. (Show a, IsString b) => a -> b
show a
count a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
" " a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
noun a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
"s " a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
what)