{- |
Module      : Conformance.Rows
Description : The 48-row consumer inventory and its validation
License     : Apache-2.0

The complete consumer-row inventory from @rows.json@: id, group,
requirement, source, expected outcome and declared plan. @list@
prints it; @validateInventory@ enforces the denominator — 48 rows,
47 owned — so a truncated inventory fails loudly instead of printing
a smaller-but-plausible table.

@executed@ is not a value @rows.json@ can carry: the declared field
is the coverage plan (@uncovered@, @bound-elsewhere@,
@out-of-scope@) and the parser rejects @executed@. A row prints as
executed only when 'effectiveState' finds a matching run receipt
(see "Conformance.Receipt").
-}
module Conformance.Rows
    ( Row (..)
    , RowState (..)
    , ShownState (..)
    , expectedRowCount
    , ownedDenominator
    , loadRows
    , validateInventory
    , effectiveState
    , renderInventory
    , renderRow
    ) where

import Data.Aeson
    ( FromJSON (..)
    , eitherDecode
    , withObject
    , withText
    , (.:)
    , (.:?)
    )
import Data.ByteString.Lazy qualified as BSL
import Data.List (nub, sort)
import Data.Text (Text)
import Data.Text qualified as T

import Conformance.Receipt (Receipt (..), Verdict (..))

{- | Total rows in @rows.json@: the 47 owned consumer rows (including
historical-permissionless-fold, the F-002 permissionless-folder regression, register-active-key, #173's
insertActive fold and its two refusal fixtures, retire-active-key, #177's
updateTerminal retirement and its two refusal fixtures, reject-and-retract-refund-controls,
issue #258's reject and retract with their tampered refunds, and reject-inside-processing-and-retraction-windows,
issue #320's early rejection in two windows) plus checkpoint-and-treasury-policy,
cardano-keri's checkpoint policy, recorded as out-of-scope so the
boundary is visible instead of forgotten. The original broader retirement
requirement stays present; permanent-retire-active-key is a separate requirement.
-}
expectedRowCount :: Int
expectedRowCount :: Int
expectedRowCount = Int
48

{- | Rows Singular owns and must eventually evidence. Out-of-scope
rows (checkpoint-and-treasury-policy) are carried for the boundary, never counted.
-}
ownedDenominator :: Int
ownedDenominator :: Int
ownedDenominator = Int
47

{- | A row's declared coverage plan. @executed@ is unrepresentable
here by construction: only a run receipt can establish it.
-}
data RowState
    = Uncovered
    | BoundElsewhere
    | OutOfScope
    deriving stock (Int -> RowState -> ShowS
[RowState] -> ShowS
RowState -> String
(Int -> RowState -> ShowS)
-> (RowState -> String) -> ([RowState] -> ShowS) -> Show RowState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RowState -> ShowS
showsPrec :: Int -> RowState -> ShowS
$cshow :: RowState -> String
show :: RowState -> String
$cshowList :: [RowState] -> ShowS
showList :: [RowState] -> ShowS
Show, RowState -> RowState -> Bool
(RowState -> RowState -> Bool)
-> (RowState -> RowState -> Bool) -> Eq RowState
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RowState -> RowState -> Bool
== :: RowState -> RowState -> Bool
$c/= :: RowState -> RowState -> Bool
/= :: RowState -> RowState -> Bool
Eq, Eq RowState
Eq RowState =>
(RowState -> RowState -> Ordering)
-> (RowState -> RowState -> Bool)
-> (RowState -> RowState -> Bool)
-> (RowState -> RowState -> Bool)
-> (RowState -> RowState -> Bool)
-> (RowState -> RowState -> RowState)
-> (RowState -> RowState -> RowState)
-> Ord RowState
RowState -> RowState -> Bool
RowState -> RowState -> Ordering
RowState -> RowState -> RowState
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 :: RowState -> RowState -> Ordering
compare :: RowState -> RowState -> Ordering
$c< :: RowState -> RowState -> Bool
< :: RowState -> RowState -> Bool
$c<= :: RowState -> RowState -> Bool
<= :: RowState -> RowState -> Bool
$c> :: RowState -> RowState -> Bool
> :: RowState -> RowState -> Bool
$c>= :: RowState -> RowState -> Bool
>= :: RowState -> RowState -> Bool
$cmax :: RowState -> RowState -> RowState
max :: RowState -> RowState -> RowState
$cmin :: RowState -> RowState -> RowState
min :: RowState -> RowState -> RowState
Ord)

instance FromJSON RowState where
    parseJSON :: Value -> Parser RowState
parseJSON = String -> (Text -> Parser RowState) -> Value -> Parser RowState
forall a. String -> (Text -> Parser a) -> Value -> Parser a
withText String
"RowState" ((Text -> Parser RowState) -> Value -> Parser RowState)
-> (Text -> Parser RowState) -> Value -> Parser RowState
forall a b. (a -> b) -> a -> b
$ \Text
t -> case Text
t of
        Text
"uncovered" -> RowState -> Parser RowState
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure RowState
Uncovered
        Text
"bound-elsewhere" -> RowState -> Parser RowState
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure RowState
BoundElsewhere
        Text
"out-of-scope" -> RowState -> Parser RowState
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure RowState
OutOfScope
        Text
"executed" ->
            String -> Parser RowState
forall a. String -> Parser a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail
                String
"rejected: executed is a receipt, not a field — \
                \rows.json carries the plan; a run receipt establishes \
                \execution"
        Text
_ -> String -> Parser RowState
forall a. String -> Parser a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String
"unknown row state: " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack Text
t)

-- | One consumer conformance row.
data Row = Row
    { Row -> Text
rowId :: !Text
    , Row -> Text
rowGroup :: !Text
    , Row -> Text
rowRequirement :: !Text
    , Row -> Text
rowSource :: !Text
    , Row -> Text
rowExpected :: !Text
    , Row -> RowState
rowState :: !RowState
    , Row -> Maybe Text
rowNote :: !(Maybe Text)
    , Row -> Maybe Text
rowEvidence :: !(Maybe Text)
    }
    deriving stock (Int -> Row -> ShowS
[Row] -> ShowS
Row -> String
(Int -> Row -> ShowS)
-> (Row -> String) -> ([Row] -> ShowS) -> Show Row
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Row -> ShowS
showsPrec :: Int -> Row -> ShowS
$cshow :: Row -> String
show :: Row -> String
$cshowList :: [Row] -> ShowS
showList :: [Row] -> ShowS
Show, Row -> Row -> Bool
(Row -> Row -> Bool) -> (Row -> Row -> Bool) -> Eq Row
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Row -> Row -> Bool
== :: Row -> Row -> Bool
$c/= :: Row -> Row -> Bool
/= :: Row -> Row -> Bool
Eq)

instance FromJSON Row where
    parseJSON :: Value -> Parser Row
parseJSON = String -> (Object -> Parser Row) -> Value -> Parser Row
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"Row" ((Object -> Parser Row) -> Value -> Parser Row)
-> (Object -> Parser Row) -> Value -> Parser Row
forall a b. (a -> b) -> a -> b
$ \Object
o ->
        Text
-> Text
-> Text
-> Text
-> Text
-> RowState
-> Maybe Text
-> Maybe Text
-> Row
Row
            (Text
 -> Text
 -> Text
 -> Text
 -> Text
 -> RowState
 -> Maybe Text
 -> Maybe Text
 -> Row)
-> Parser Text
-> Parser
     (Text
      -> Text
      -> Text
      -> Text
      -> RowState
      -> Maybe Text
      -> Maybe Text
      -> Row)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"id"
            Parser
  (Text
   -> Text
   -> Text
   -> Text
   -> RowState
   -> Maybe Text
   -> Maybe Text
   -> Row)
-> Parser Text
-> Parser
     (Text
      -> Text -> Text -> RowState -> Maybe Text -> Maybe Text -> Row)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"group"
            Parser
  (Text
   -> Text -> Text -> RowState -> Maybe Text -> Maybe Text -> Row)
-> Parser Text
-> Parser
     (Text -> Text -> RowState -> Maybe Text -> Maybe Text -> Row)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"requirement"
            Parser
  (Text -> Text -> RowState -> Maybe Text -> Maybe Text -> Row)
-> Parser Text
-> Parser (Text -> RowState -> Maybe Text -> Maybe Text -> Row)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"source"
            Parser (Text -> RowState -> Maybe Text -> Maybe Text -> Row)
-> Parser Text
-> Parser (RowState -> Maybe Text -> Maybe Text -> Row)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"expected"
            Parser (RowState -> Maybe Text -> Maybe Text -> Row)
-> Parser RowState -> Parser (Maybe Text -> Maybe Text -> Row)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser RowState
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"state"
            Parser (Maybe Text -> Maybe Text -> Row)
-> Parser (Maybe Text) -> Parser (Maybe Text -> Row)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"note"
            Parser (Maybe Text -> Row) -> Parser (Maybe Text) -> Parser Row
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"evidence"

newtype Inventory = Inventory {Inventory -> [Row]
inventoryRows :: [Row]}

instance FromJSON Inventory where
    parseJSON :: Value -> Parser Inventory
parseJSON = String -> (Object -> Parser Inventory) -> Value -> Parser Inventory
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"Inventory" ((Object -> Parser Inventory) -> Value -> Parser Inventory)
-> (Object -> Parser Inventory) -> Value -> Parser Inventory
forall a b. (a -> b) -> a -> b
$ \Object
o ->
        [Row] -> Inventory
Inventory ([Row] -> Inventory) -> Parser [Row] -> Parser Inventory
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser [Row]
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"rows"

-- | Load and validate the inventory file.
loadRows :: FilePath -> IO (Either String [Row])
loadRows :: String -> IO (Either String [Row])
loadRows String
path = do
    content <- String -> IO ByteString
BSL.readFile String
path
    pure $ case eitherDecode content of
        Left String
err -> String -> Either String [Row]
forall a b. a -> Either a b
Left (String
"rows.json does not parse: " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
err)
        Right Inventory
inv -> [Row] -> Either String [Row]
validateInventory (Inventory -> [Row]
inventoryRows Inventory
inv)

{- | Enforce the denominator: exactly 'expectedRowCount' rows of which
'exactly' 'ownedDenominator' are owned, unique ids, non-empty groups.
A count over a silently shortened set is a lower bound wearing the
denominator's name, so short is an error, not a smaller table.
-}
validateInventory :: [Row] -> Either String [Row]
validateInventory :: [Row] -> Either String [Row]
validateInventory [Row]
rows
    | [Row] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Row]
rows = String -> Either String [Row]
forall a b. a -> Either a b
Left String
"inventory is empty"
    | [Row] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Row]
rows Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
expectedRowCount =
        String -> Either String [Row]
forall a b. a -> Either a b
Left
            ( String
"inventory carries "
                String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall a. Show a => a -> String
show ([Row] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Row]
rows)
                String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" rows, expected "
                String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall a. Show a => a -> String
show Int
expectedRowCount
            )
    | Int
owned Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
ownedDenominator =
        String -> Either String [Row]
forall a b. a -> Either a b
Left
            ( String
"inventory owns "
                String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall a. Show a => a -> String
show Int
owned
                String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" rows, expected owned denominator "
                String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall a. Show a => a -> String
show Int
ownedDenominator
            )
    | [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Text] -> [Text]
forall a. Eq a => [a] -> [a]
nub [Text]
ids) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
ids =
        String -> Either String [Row]
forall a b. a -> Either a b
Left String
"inventory has duplicate row ids"
    | (Row -> Bool) -> [Row] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text -> Bool
T.null (Text -> Bool) -> (Row -> Text) -> Row -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Row -> Text
rowGroup) [Row]
rows =
        String -> Either String [Row]
forall a b. a -> Either a b
Left String
"inventory has a row with an empty group"
    | Bool
otherwise = [Row] -> Either String [Row]
forall a b. b -> Either a b
Right [Row]
rows
  where
    ids :: [Text]
ids = (Row -> Text) -> [Row] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Row -> Text
rowId [Row]
rows
    owned :: Int
owned =
        [Row] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length
            ((Row -> Bool) -> [Row] -> [Row]
forall a. (a -> Bool) -> [a] -> [a]
filter ((RowState -> RowState -> Bool
forall a. Eq a => a -> a -> Bool
/= RowState
OutOfScope) (RowState -> Bool) -> (Row -> RowState) -> Row -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Row -> RowState
rowState) [Row]
rows)

{- | What @list@ prints for a row: executed iff a receipt for it
exists, matches the current base and agrees with the model; partial for
a partial receipt; the verdict a receipt recorded when it is neither (a
held, divergent or ruled-on run is never a pass); else the declared plan.
-}
data ShownState
    = ShownExecuted
    | ShownPartial
    | ShownRecorded Verdict
    | ShownPlanned RowState
    deriving stock (Int -> ShownState -> ShowS
[ShownState] -> ShowS
ShownState -> String
(Int -> ShownState -> ShowS)
-> (ShownState -> String)
-> ([ShownState] -> ShowS)
-> Show ShownState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ShownState -> ShowS
showsPrec :: Int -> ShownState -> ShowS
$cshow :: ShownState -> String
show :: ShownState -> String
$cshowList :: [ShownState] -> ShowS
showList :: [ShownState] -> ShowS
Show, ShownState -> ShownState -> Bool
(ShownState -> ShownState -> Bool)
-> (ShownState -> ShownState -> Bool) -> Eq ShownState
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ShownState -> ShownState -> Bool
== :: ShownState -> ShownState -> Bool
$c/= :: ShownState -> ShownState -> Bool
/= :: ShownState -> ShownState -> Bool
Eq)

effectiveState :: Text -> [Receipt] -> Row -> ShownState
effectiveState :: Text -> [Receipt] -> Row -> ShownState
effectiveState Text
base [Receipt]
receipts Row
row =
    case [ Receipt
r
         | Receipt
r <- [Receipt]
receipts
         , Receipt -> Text
receiptRow Receipt
r Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Row -> Text
rowId Row
row
         , Receipt -> Text
receiptBase Receipt
r Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
base
         ] of
        (Receipt
r : [Receipt]
_) -> case Receipt -> Verdict
receiptVerdict Receipt
r of
            Verdict
AgreesWithModel -> ShownState
ShownExecuted
            Verdict
Partial -> ShownState
ShownPartial
            Verdict
other -> Verdict -> ShownState
ShownRecorded Verdict
other
        [] -> RowState -> ShownState
ShownPlanned (Row -> RowState
rowState Row
row)

{- | Render the full inventory table plus the state summary. The
summary counts effective states and names the owned denominator
with the out-of-scope rows excluded from it.
-}
renderInventory :: Text -> [Receipt] -> [Row] -> Text
renderInventory :: Text -> [Receipt] -> [Row] -> Text
renderInventory Text
base [Receipt]
receipts [Row]
rows =
    [Text] -> Text
T.unlines
        ( Text
header
            Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: (Row -> Text) -> [Row] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map ((Row, ShownState) -> Text
renderRow ((Row, ShownState) -> Text)
-> (Row -> (Row, ShownState)) -> Row -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Row -> (Row, ShownState)
shown) [Row]
sorted
                [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> [Text
"", Text
summary, Text
ownedLine]
        )
  where
    sorted :: [Row]
sorted = [Row] -> [Row]
forall a. Ord a => [a] -> [a]
sort [Row]
rows
    shown :: Row -> (Row, ShownState)
shown Row
r = (Row
r, Text -> [Receipt] -> Row -> ShownState
effectiveState Text
base [Receipt]
receipts Row
r)
    header :: Text
header =
        Text
"id\tgroup\texpected\tstate\trequirement"
    states :: [ShownState]
states =
        [ ShownState
ShownExecuted
        , ShownState
ShownPartial
        , Verdict -> ShownState
ShownRecorded Verdict
HeldQ002
        , Verdict -> ShownState
ShownRecorded Verdict
DivergesFromLean
        , Verdict -> ShownState
ShownRecorded Verdict
ResolvedByRuling
        , Verdict -> ShownState
ShownRecorded Verdict
UnmetByRuling
        , RowState -> ShownState
ShownPlanned RowState
BoundElsewhere
        , RowState -> ShownState
ShownPlanned RowState
Uncovered
        , RowState -> ShownState
ShownPlanned RowState
OutOfScope
        ]
    summary :: Text
summary =
        String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show ([Row] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Row]
rows))
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" rows: "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
", " ((ShownState -> Text) -> [ShownState] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map ShownState -> Text
countFor [ShownState]
states)
    countFor :: ShownState -> Text
countFor ShownState
s =
        String -> Text
T.pack
            ( Int -> String
forall a. Show a => a -> String
show
                ( [Row] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length
                    ( (Row -> Bool) -> [Row] -> [Row]
forall a. (a -> Bool) -> [a] -> [a]
filter
                        ((ShownState -> ShownState -> Bool
forall a. Eq a => a -> a -> Bool
== ShownState
s) (ShownState -> Bool) -> (Row -> ShownState) -> Row -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Receipt] -> Row -> ShownState
effectiveState Text
base [Receipt]
receipts)
                        [Row]
rows
                    )
                )
            )
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ShownState -> Text
shownName ShownState
s
    ownedLine :: Text
ownedLine =
        Text
"owned denominator: "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show Int
ownedDenominator)
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ("
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate
                Text
", "
                [ Row -> Text
rowId Row
r
                | Row
r <- [Row]
rows
                , Row -> RowState
rowState Row
r RowState -> RowState -> Bool
forall a. Eq a => a -> a -> Bool
== RowState
OutOfScope
                ]
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" out of scope)"

renderRow :: (Row, ShownState) -> Text
renderRow :: (Row, ShownState) -> Text
renderRow (Row
r, ShownState
s) =
    Text -> [Text] -> Text
T.intercalate
        Text
"\t"
        [ Row -> Text
rowId Row
r
        , Row -> Text
rowGroup Row
r
        , Row -> Text
rowExpected Row
r
        , ShownState -> Text
shownName ShownState
s
        , Row -> Text
rowRequirement Row
r
        ]

shownName :: ShownState -> Text
shownName :: ShownState -> Text
shownName ShownState
ShownExecuted = Text
"executed"
shownName ShownState
ShownPartial = Text
"partial"
shownName (ShownRecorded Verdict
HeldQ002) = Text
"held"
shownName (ShownRecorded Verdict
DivergesFromLean) = Text
"diverges"
shownName (ShownRecorded Verdict
ResolvedByRuling) = Text
"resolved-by-ruling"
shownName (ShownRecorded Verdict
UnmetByRuling) = Text
"unmet-by-ruling"
shownName (ShownRecorded Verdict
AgreesWithModel) = Text
"executed"
shownName (ShownRecorded Verdict
Partial) = Text
"partial"
shownName (ShownPlanned RowState
Uncovered) = Text
"uncovered"
shownName (ShownPlanned RowState
BoundElsewhere) = Text
"bound-elsewhere"
shownName (ShownPlanned RowState
OutOfScope) = Text
"out-of-scope"

-- Rows sort by id for a stable table.
instance Ord Row where
    compare :: Row -> Row -> Ordering
compare Row
a Row
b = Text -> Text -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (Row -> Text
rowId Row
a) (Row -> Text
rowId Row
b)