keiki-0.2.0.0: test/Keiki/Fixtures/SplitCoverage.hs
-- | Shared fixture, integration point 4 of
-- @docs/masterplans/16-harden-keiki-correctness-and-api-surfaces-surfaced-by-the-2026-07-architecture-review.md@:
-- consumed by EP-71 (validation alignment), EP-73 (round-trip property
-- harness), and EP-74 (composition semantics). Do not fold into a spec module.
module Keiki.Fixtures.SplitCoverage
( SplitCmd (..),
SplitEvent (..),
inCtorBegin,
wireOutAB,
wireOutBC,
wireOutABC,
wireOutA,
splitCoverageBad,
splitCoverageFixed,
splitCoverageUnionMiss,
splitCoverageSingleMiss,
)
where
import Data.Proxy (Proxy (..))
import Keiki.Core
-- | One three-field command used to expose the difference between union
-- coverage and replay's head-only inversion contract.
data SplitCmd = Begin Int Int Int
deriving stock (Eq, Show)
-- | Event constructors for complete, split, and incomplete coverage shapes.
data SplitEvent
= OutAB Int Int
| OutBC Int Int
| OutABC Int Int Int
| OutA Int
deriving stock (Eq, Show)
type BeginFields =
'[ '("a", Int),
'("b", Int),
'("c", Int)
]
inCtorBegin :: InCtor SplitCmd BeginFields
inCtorBegin =
InCtor
{ icName = "Begin",
icMatch = \case
Begin a b c ->
Just $
RCons (Proxy @"a") a $
RCons (Proxy @"b") b $
RCons (Proxy @"c") c RNil,
icBuild = \(RCons _ a (RCons _ b (RCons _ c RNil))) -> Begin a b c
}
wireOutAB :: WireCtor SplitEvent (Int, (Int, ()))
wireOutAB =
WireCtor
{ wcName = "OutAB",
wcMatch = \case OutAB a b -> Just (a, (b, ())); _ -> Nothing,
wcBuild = \(a, (b, ())) -> OutAB a b
}
wireOutBC :: WireCtor SplitEvent (Int, (Int, ()))
wireOutBC =
WireCtor
{ wcName = "OutBC",
wcMatch = \case OutBC b c -> Just (b, (c, ())); _ -> Nothing,
wcBuild = \(b, (c, ())) -> OutBC b c
}
wireOutABC :: WireCtor SplitEvent (Int, (Int, (Int, ())))
wireOutABC =
WireCtor
{ wcName = "OutABC",
wcMatch = \case OutABC a b c -> Just (a, (b, (c, ()))); _ -> Nothing,
wcBuild = \(a, (b, (c, ()))) -> OutABC a b c
}
wireOutA :: WireCtor SplitEvent (Int, ())
wireOutA =
WireCtor
{ wcName = "OutA",
wcMatch = \case OutA a -> Just (a, ()); _ -> Nothing,
wcBuild = \(a, ()) -> OutA a
}
beginA :: Term '[] SplitCmd BeginFields Int
beginA = TInpCtorField inCtorBegin (#a :: Index BeginFields Int)
beginB :: Term '[] SplitCmd BeginFields Int
beginB = TInpCtorField inCtorBegin (#b :: Index BeginFields Int)
beginC :: Term '[] SplitCmd BeginFields Int
beginC = TInpCtorField inCtorBegin (#c :: Index BeginFields Int)
splitTransducer :: [OutTerm '[] SplitCmd SplitEvent] -> SymTransducer (HsPred '[] SplitCmd) '[] Bool SplitCmd SplitEvent
splitTransducer outputs =
SymTransducer
{ edgesOut = \case
False ->
[ Edge
{ guard = matchInCtor inCtorBegin,
update = UKeep,
output = outputs,
target = True
}
]
True -> [],
initial = False,
initialRegs = RNil,
isFinal = id
}
-- | Defective shape: the output union covers @a,b,c@, but the head covers
-- only @a,b@, so replay cannot reconstruct @Begin@ from the first event.
splitCoverageBad :: SymTransducer (HsPred '[] SplitCmd) '[] Bool SplitCmd SplitEvent
splitCoverageBad =
splitTransducer
[ pack inCtorBegin wireOutAB (beginA *: beginB *: oNil),
pack inCtorBegin wireOutBC (beginB *: beginC *: oNil)
]
-- | Repaired shape: the head event alone covers all command fields.
splitCoverageFixed :: SymTransducer (HsPred '[] SplitCmd) '[] Bool SplitCmd SplitEvent
splitCoverageFixed =
splitTransducer
[ pack inCtorBegin wireOutABC (beginA *: beginB *: beginC *: oNil),
pack inCtorBegin wireOutBC (beginB *: beginC *: oNil)
]
-- | The output union still omits @c@ entirely.
splitCoverageUnionMiss :: SymTransducer (HsPred '[] SplitCmd) '[] Bool SplitCmd SplitEvent
splitCoverageUnionMiss =
splitTransducer
[ pack inCtorBegin wireOutAB (beginA *: beginB *: oNil),
pack inCtorBegin wireOutA (beginA *: oNil)
]
-- | A one-event edge that omits @b@ and @c@.
splitCoverageSingleMiss :: SymTransducer (HsPred '[] SplitCmd) '[] Bool SplitCmd SplitEvent
splitCoverageSingleMiss =
splitTransducer [pack inCtorBegin wireOutA (beginA *: oNil)]