keiki-0.7.0.0: test/Keiki/FieldProjSpec.hs
{-# LANGUAGE TypeFamilies #-}
module Keiki.FieldProjSpec where
import Data.Foldable (toList)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (isNothing)
import Data.Proxy (Proxy (..))
import Data.SBV qualified as SBV
import Data.Text (Text)
import Data.Text qualified as T
import Keiki.Core
import Keiki.ProjectionDomain
import Keiki.Symbolic
( DeadEdgeAnalysisDetail (..),
DeterminismAnalysisDetail (..),
KnownInCtors (..),
PredicateVerification (..),
PredicateVerificationDetail (..),
ProjectionBaseDescriptor (..),
ProjectionDescriptor (..),
ProjectionModel (..),
SomeInCtor (..),
SymEnv (..),
TranslationIssue (..),
TranslationStrength (..),
checkDeadEdgesSym,
checkDeadEdgesSymDetailed,
checkTransitionDeterminismSym,
checkTransitionDeterminismSymDetailed,
constrainFieldProjection,
mkSymEnv,
predicateTranslationExact,
predicateTranslationReport,
projectionModelKeyAs,
projectionModelOwnerAs,
symIsBot,
symSatExt,
translatePred,
verifyPredicate,
verifyPredicateDetailed,
)
import Test.Hspec
import Test.QuickCheck
( expectFailure,
ioProperty,
property,
)
import Test.QuickCheck.Property (withMaxSuccess)
data DocInfo = DocInfo
{ diHash :: Text,
diTitle :: Text,
diNumbers :: [Int]
}
deriving stock (Eq, Show)
data DocContentHash
instance FieldProjection DocContentHash where
type FieldName DocContentHash = "contentHash"
type FieldOwner DocContentHash = DocInfo
type FieldResult DocContentHash = Text
fieldShapeId _ = "test.doc-info.v1"
projectFieldValue _ = diHash
data DocTitle
instance FieldProjection DocTitle where
type FieldName DocTitle = "title"
type FieldOwner DocTitle = DocInfo
type FieldResult DocTitle = Text
fieldShapeId _ = "test.doc-info.v1"
projectFieldValue _ = diTitle
data DocContentHashAlias
instance FieldProjection DocContentHashAlias where
type FieldName DocContentHashAlias = "contentHash"
type FieldOwner DocContentHashAlias = DocInfo
type FieldResult DocContentHashAlias = Text
fieldShapeId _ = "test.doc-info.v1"
projectFieldValue _ = diHash
data DocNumbers
instance FieldProjection DocNumbers where
type FieldName DocNumbers = "numbers"
type FieldOwner DocNumbers = DocInfo
type FieldResult DocNumbers = [Int]
fieldShapeId _ = "test.doc-info.v1"
projectFieldValue _ = diNumbers
data DocIdentity
instance FieldProjection DocIdentity where
type FieldName DocIdentity = "self"
type FieldOwner DocIdentity = DocInfo
type FieldResult DocIdentity = DocInfo
fieldShapeId _ = "test.doc-info.v1"
projectFieldValue _ = id
data AdversarialHash
instance FieldProjection AdversarialHash where
type FieldName AdversarialHash = "content/|\\hash"
type FieldOwner AdversarialHash = DocInfo
type FieldResult AdversarialHash = Text
fieldShapeId _ = "shape/|\\doc"
projectFieldValue _ = diHash
docHashW :: FieldWitness DocContentHash
docHashW = fieldWitness @DocContentHash
docTitleW :: FieldWitness DocTitle
docTitleW = fieldWitness @DocTitle
docHashAliasW :: FieldWitness DocContentHashAlias
docHashAliasW = fieldWitness @DocContentHashAlias
docNumbersW :: FieldWitness DocNumbers
docNumbersW = fieldWitness @DocNumbers
docIdentityW :: FieldWitness DocIdentity
docIdentityW = fieldWitness @DocIdentity
adversarialHashW :: FieldWitness AdversarialHash
adversarialHashW = fieldWitness @AdversarialHash
type DocRegs = '[ '("doc", DocInfo)]
docIx :: Index DocRegs DocInfo
docIx = #doc
docN :: IndexN "doc" DocRegs DocInfo
docN = IZ
data DocCmd = NewDoc DocInfo
deriving stock (Eq, Show)
type NewDocFields = '[ '("doc", DocInfo)]
newDocCtor :: InCtor DocCmd NewDocFields
newDocCtor =
InCtor
{ icName = "NewDoc",
icMatch = \case
NewDoc doc -> Just (RCons (Proxy @"doc") doc RNil),
icBuild = \(RCons _ doc RNil) -> NewDoc doc
}
data DocEvent = DocAccepted DocInfo
deriving stock (Eq, Show)
docAcceptedWire :: WireCtor DocEvent (DocInfo, ())
docAcceptedWire =
WireCtor
{ wcName = "DocAccepted",
wcMatch = \case DocAccepted doc -> Just (doc, ()),
wcBuild = \(doc, ()) -> DocAccepted doc
}
data DocState = DocState
deriving stock (Eq, Ord, Show, Enum, Bounded)
initialDocInfo :: DocInfo
initialDocInfo = DocInfo "old-hash" "old title" []
docProjectionTransducer ::
SymTransducer (HsPred DocRegs DocCmd) DocRegs DocState DocCmd DocEvent
docProjectionTransducer =
SymTransducer
{ edgesOut = \DocState ->
[ Edge
{ guard =
PAnd
(matchInCtor newDocCtor)
(regProj docHashW docIx ./= inpProj docHashW newDocCtor #doc),
update = USet docN (TInpCtorField newDocCtor #doc),
output =
[ pack
newDocCtor
docAcceptedWire
(OFCons (TInpCtorField newDocCtor #doc) OFNil)
],
target = DocState,
mode = Live
}
],
initial = DocState,
initialRegs = RCons (Proxy @"doc") initialDocInfo RNil,
isFinal = const True
}
inputProjectionTransducer ::
SymTransducer (HsPred '[] DocCmd) '[] DocState DocCmd DocEvent
inputProjectionTransducer =
SymTransducer
{ edgesOut = \DocState ->
[ Edge
{ guard =
PAnd
(matchInCtor newDocCtor)
(inpProj docHashW newDocCtor #doc .== TLit "new-hash"),
update = UKeep,
output =
[ pack
newDocCtor
docAcceptedWire
(OFCons (TInpCtorField newDocCtor #doc) OFNil)
],
target = DocState,
mode = Live
}
],
initial = DocState,
initialRegs = RNil,
isFinal = const True
}
data PairInts = PairInts Int Int
deriving stock (Eq, Show)
data WrongFirst
instance FieldProjection WrongFirst where
type FieldName WrongFirst = "first"
type FieldOwner WrongFirst = PairInts
type FieldResult WrongFirst = Int
fieldShapeId _ = "test.pair-ints.v1"
projectFieldValue _ (PairInts _ second) = second
wrongFirstW :: FieldWitness WrongFirst
wrongFirstW = fieldWitness @WrongFirst
data NumberOwner = NumberOwner Int Integer
data NumberAsInt
instance FieldProjection NumberAsInt where
type FieldName NumberAsInt = "number"
type FieldOwner NumberAsInt = NumberOwner
type FieldResult NumberAsInt = Int
fieldShapeId _ = "test.number-owner.v1"
projectFieldValue _ (NumberOwner value _) = value
data NumberAsInteger
instance FieldProjection NumberAsInteger where
type FieldName NumberAsInteger = "number"
type FieldOwner NumberAsInteger = NumberOwner
type FieldResult NumberAsInteger = Integer
fieldShapeId _ = "test.number-owner.v1"
projectFieldValue _ (NumberOwner _ value) = value
type NumberRegs = '[ '("numberOwner", NumberOwner)]
numberIntW :: FieldWitness NumberAsInt
numberIntW = fieldWitness @NumberAsInt
numberIntegerW :: FieldWitness NumberAsInteger
numberIntegerW = fieldWitness @NumberAsInteger
data BoolKey
instance FieldProjection BoolKey where
type FieldName BoolKey = "key"
type FieldOwner BoolKey = Bool
type FieldResult BoolKey = Text
fieldShapeId _ = "test.bool-key.v1"
projectFieldValue _ False = "disabled"
projectFieldValue _ True = "enabled"
instance ExactFieldProjection BoolKey where
fieldProjectionDomain _ =
finiteProjectionDomain ("disabled" :| ["enabled"])
reconstructFieldOwner _ "disabled" = Just False
reconstructFieldOwner _ "enabled" = Just True
reconstructFieldOwner _ _ = Nothing
type BoolOwnerRegs = '[ '("owner", Bool)]
boolKeyW :: FieldWitness BoolKey
boolKeyW = fieldWitness @BoolKey
exactBoolKeyW :: FieldWitness BoolKey
exactBoolKeyW = exactFieldWitness @BoolKey
boolOwnerIx :: Index BoolOwnerRegs Bool
boolOwnerIx = #owner
exhaustiveProjectionPredicate :: HsPred BoolOwnerRegs ()
exhaustiveProjectionPredicate =
PAnd
(regProj boolKeyW boolOwnerIx ./= TLit "disabled")
(regProj boolKeyW boolOwnerIx ./= TLit "enabled")
repeatedProjectionContradiction :: HsPred BoolOwnerRegs ()
repeatedProjectionContradiction =
regProj boolKeyW boolOwnerIx ./= regProj boolKeyW boolOwnerIx
exactExhaustiveProjectionPredicate :: HsPred BoolOwnerRegs ()
exactExhaustiveProjectionPredicate =
PAnd
(regProj exactBoolKeyW boolOwnerIx ./= TLit "disabled")
(regProj exactBoolKeyW boolOwnerIx ./= TLit "enabled")
exactEnabledProjectionPredicate :: HsPred BoolOwnerRegs ()
exactEnabledProjectionPredicate =
regProj exactBoolKeyW boolOwnerIx .== TLit "enabled"
data BoolAsInt
instance FieldProjection BoolAsInt where
type FieldName BoolAsInt = "asInt"
type FieldOwner BoolAsInt = Bool
type FieldResult BoolAsInt = Int
fieldShapeId _ = "test.bool-as-int.v1"
projectFieldValue _ False = 0
projectFieldValue _ True = 1
instance ExactFieldProjection BoolAsInt where
fieldProjectionDomain _ = finiteProjectionDomain (0 :| [1])
reconstructFieldOwner _ 0 = Just False
reconstructFieldOwner _ 1 = Just True
reconstructFieldOwner _ _ = Nothing
exactBoolAsIntW :: FieldWitness BoolAsInt
exactBoolAsIntW = exactFieldWitness @BoolAsInt
data RichOwner = RichOwner Bool Bool
deriving stock (Eq, Show)
data RichFirst
instance FieldProjection RichFirst where
type FieldName RichFirst = "first"
type FieldOwner RichFirst = RichOwner
type FieldResult RichFirst = Bool
fieldShapeId _ = "test.rich-owner.v1"
projectFieldValue _ (RichOwner first _) = first
instance ExactFieldProjection RichFirst where
fieldProjectionDomain _ = wholeProjectionDomain
reconstructFieldOwner _ first = Just (RichOwner first False)
data RichSecond
instance FieldProjection RichSecond where
type FieldName RichSecond = "second"
type FieldOwner RichSecond = RichOwner
type FieldResult RichSecond = Bool
fieldShapeId _ = "test.rich-owner.v1"
projectFieldValue _ (RichOwner _ second) = second
instance ExactFieldProjection RichSecond where
fieldProjectionDomain _ = wholeProjectionDomain
reconstructFieldOwner _ second = Just (RichOwner False second)
exactRichFirstW :: FieldWitness RichFirst
exactRichFirstW = exactFieldWitness @RichFirst
exactRichSecondW :: FieldWitness RichSecond
exactRichSecondW = exactFieldWitness @RichSecond
type RichRegs = '[ '("left", RichOwner), '("right", RichOwner)]
richLeftIx :: Index RichRegs RichOwner
richLeftIx = #left
richRightIx :: Index RichRegs RichOwner
richRightIx = #right
data BrokenRejectInverse
instance FieldProjection BrokenRejectInverse where
type FieldName BrokenRejectInverse = "brokenReject"
type FieldOwner BrokenRejectInverse = Bool
type FieldResult BrokenRejectInverse = Text
fieldShapeId _ = "test.broken-reject.v1"
projectFieldValue _ False = "disabled"
projectFieldValue _ True = "enabled"
instance ExactFieldProjection BrokenRejectInverse where
fieldProjectionDomain _ = finiteProjectionDomain ("enabled" :| [])
reconstructFieldOwner _ _ = Nothing
data BrokenRoundTripInverse
instance FieldProjection BrokenRoundTripInverse where
type FieldName BrokenRoundTripInverse = "brokenRoundTrip"
type FieldOwner BrokenRoundTripInverse = Bool
type FieldResult BrokenRoundTripInverse = Text
fieldShapeId _ = "test.broken-round-trip.v1"
projectFieldValue _ False = "disabled"
projectFieldValue _ True = "enabled"
instance ExactFieldProjection BrokenRoundTripInverse where
fieldProjectionDomain _ = finiteProjectionDomain ("enabled" :| [])
reconstructFieldOwner _ _ = Just False
data UnderDeclaredDomain
instance FieldProjection UnderDeclaredDomain where
type FieldName UnderDeclaredDomain = "underDeclared"
type FieldOwner UnderDeclaredDomain = Bool
type FieldResult UnderDeclaredDomain = Text
fieldShapeId _ = "test.under-declared.v1"
projectFieldValue _ False = "disabled"
projectFieldValue _ True = "enabled"
instance ExactFieldProjection UnderDeclaredDomain where
fieldProjectionDomain _ = finiteProjectionDomain ("disabled" :| [])
reconstructFieldOwner _ "disabled" = Just False
reconstructFieldOwner _ _ = Nothing
data BoolProjectionCmd = WithOwner Bool | WithoutOwner
deriving stock (Eq, Show)
type WithOwnerFields = '[ '("owner", Bool)]
withOwnerCtor :: InCtor BoolProjectionCmd WithOwnerFields
withOwnerCtor =
InCtor
{ icName = "WithOwner",
icMatch = \case
WithOwner owner -> Just (RCons (Proxy @"owner") owner RNil)
WithoutOwner -> Nothing,
icBuild = \(RCons _ owner RNil) -> WithOwner owner
}
withoutOwnerCtor :: InCtor BoolProjectionCmd '[]
withoutOwnerCtor =
InCtor
{ icName = "WithoutOwner",
icMatch = \case
WithoutOwner -> Just RNil
WithOwner _ -> Nothing,
icBuild = \RNil -> WithoutOwner
}
instance KnownInCtors BoolProjectionCmd where
allInCtors = [SomeInCtor withOwnerCtor, SomeInCtor withoutOwnerCtor]
proveConcreteAgreement ::
HsPred rs ci ->
(SymEnv -> SBV.Symbolic ()) ->
Bool ->
IO Bool
proveConcreteAgreement predicate bindConcrete concrete = do
result <- SBV.prove $ do
env <- mkSymEnv
translated <- translatePred env predicate
bindConcrete env
pure (translated SBV..<=> SBV.literal concrete)
pure (not (SBV.modelExists result))
translationIssuesOf :: HsPred rs ci -> [TranslationIssue]
translationIssuesOf predicate = case predicateTranslationReport predicate of
ExactTranslation -> []
ConservativeOverApproximation issues -> toList issues
isConflictingViews :: TranslationIssue -> Bool
isConflictingViews ConflictingProjectionViews {} = True
isConflictingViews _ = False
isDirectAndProjected :: TranslationIssue -> Bool
isDirectAndProjected DirectAndProjectedOwnerRead {} = True
isDirectAndProjected _ = False
isUnguardedProjection :: TranslationIssue -> Bool
isUnguardedProjection UnguardedProjectionInputRead {} = True
isUnguardedProjection _ = False
isOutsideEquality :: TranslationIssue -> Bool
isOutsideEquality ProjectionUsedOutsideEquality {} = True
isOutsideEquality _ = False
data ProjectionAnalysisState = ProjectionAnalysisState
deriving stock (Eq, Show, Enum, Bounded)
projectionAnalysisEdge ::
HsPred BoolOwnerRegs () ->
Edge (HsPred BoolOwnerRegs ()) BoolOwnerRegs () () ProjectionAnalysisState
projectionAnalysisEdge edgeGuard =
Edge
{ guard = edgeGuard,
update = UKeep,
output = [],
target = ProjectionAnalysisState,
mode = Live
}
projectionDeterminismTransducer ::
SymTransducer
(HsPred BoolOwnerRegs ())
BoolOwnerRegs
ProjectionAnalysisState
()
()
projectionDeterminismTransducer =
SymTransducer
{ edgesOut = \ProjectionAnalysisState ->
[ projectionAnalysisEdge
(regProj exactBoolKeyW boolOwnerIx .== TLit "disabled"),
projectionAnalysisEdge
(regProj exactBoolKeyW boolOwnerIx .== TLit "enabled")
],
initial = ProjectionAnalysisState,
initialRegs = RCons (Proxy @"owner") False RNil,
isFinal = const True
}
projectionDeadEdgeTransducer ::
SymTransducer
(HsPred BoolOwnerRegs ())
BoolOwnerRegs
ProjectionAnalysisState
()
()
projectionDeadEdgeTransducer =
SymTransducer
{ edgesOut = \ProjectionAnalysisState ->
[projectionAnalysisEdge exactExhaustiveProjectionPredicate],
initial = ProjectionAnalysisState,
initialRegs = RCons (Proxy @"owner") False RNil,
isFinal = const True
}
spec :: Spec
spec = do
describe "concrete field projection" $ do
let doc = DocInfo "hash-1" "title-1" [1, 2]
regs = RCons (Proxy @"doc") doc RNil
it "evaluates a register-owned field" $
evalTerm (regProj docHashW docIx :: Term DocRegs DocCmd '[] Text) regs (NewDoc doc)
`shouldBe` "hash-1"
it "evaluates an input-owned field" $
evalTerm
(inpProj docHashW newDocCtor #doc :: Term '[] DocCmd NewDocFields Text)
RNil
(NewDoc doc)
`shouldBe` "hash-1"
it "keeps projection guards out of the opaque audit while TApp1 remains opaque" $ do
opaqueGuardWarnings docProjectionTransducer `shouldBe` []
let opaque =
docProjectionTransducer
{ edgesOut = \DocState ->
[ Edge
{ guard = PEq (TApp1 diHash (TReg docIx)) (TLit "old-hash"),
update = UKeep,
output = [],
target = DocState,
mode = Live
}
]
}
opaqueGuardWarnings opaque `shouldSatisfy` (not . null)
describe "path-keyed symbolic projection" $ do
it "shares one variable for the same nominal projection and base" $
symIsBot
( regProj docHashW docIx ./= regProj docHashW docIx ::
HsPred DocRegs ()
)
`shouldBe` True
it "keeps distinct fields of one owner independent" $
symIsBot
( PAnd
(regProj docHashW docIx .== TLit "left")
(regProj docTitleW docIx .== TLit "right") ::
HsPred DocRegs ()
)
`shouldBe` False
it "keeps nominal tags independent even with identical diagnostics" $
symIsBot
( PAnd
(regProj docHashW docIx .== TLit "left")
(regProj docHashAliasW docIx .== TLit "right") ::
HsPred DocRegs ()
)
`shouldBe` False
it "keeps Int and Integer results independent despite a shared SBV representation" $
symIsBot
( PAnd
(regProj numberIntW (#numberOwner :: Index NumberRegs NumberOwner) .== TLit 0)
(regProj numberIntegerW (#numberOwner :: Index NumberRegs NumberOwner) .== TLit 1) ::
HsPred NumberRegs ()
)
`shouldBe` False
it "uses index position when duplicate diagnostic labels are constructed manually" $ do
let first = ZIdx :: Index '[ '("doc", DocInfo), '("doc", DocInfo)] DocInfo
second = SIdx ZIdx :: Index '[ '("doc", DocInfo), '("doc", DocInfo)] DocInfo
symIsBot
( PAnd
(regProj docHashW first .== TLit "left")
(regProj docHashW second .== TLit "right") ::
HsPred '[ '("doc", DocInfo), '("doc", DocInfo)] ()
)
`shouldBe` False
it "keeps register and input bases distinct even when dotted paths coincide" $ do
let registerBase = ZIdx :: Index '[ '("NewDoc.doc", DocInfo)] DocInfo
symIsBot
( PAnd
(regProj docHashW registerBase .== TLit "left")
(inpProj docHashW newDocCtor #doc .== TLit "right") ::
HsPred '[ '("NewDoc.doc", DocInfo)] DocCmd
)
`shouldBe` False
it "never sends adversarial diagnostic strings to SBV labels" $
let adversarialIx = ZIdx :: Index '[ '("doc/|\\owner", DocInfo)] DocInfo
in symIsBot
( regProj adversarialHashW adversarialIx
./= regProj adversarialHashW adversarialIx ::
HsPred '[ '("doc/|\\owner", DocInfo)] ()
)
`shouldBe` True
describe "projection verification" $ do
it "checks every owner and key in the finite exact image" $
mapM_
( \(owner, key) -> do
checkFieldProjectionOwner exactBoolKeyW owner `shouldBe` Right ()
checkFieldProjectionKey exactBoolKeyW key `shouldBe` Right owner
)
[(False, "disabled"), (True, "enabled")]
it "does not call a supported but unconstrained projection translation-exact" $ do
-- Before the conservative classifier, z3 could invent a third Text key
-- outside the getter's two-value image and verify that fabricated model.
evalPred
exhaustiveProjectionPredicate
(RCons (Proxy @"owner") False RNil)
()
`shouldBe` False
evalPred
exhaustiveProjectionPredicate
(RCons (Proxy @"owner") True RNil)
()
`shouldBe` False
fieldWitnessHasExactDomain boolKeyW `shouldBe` False
predicateTranslationExact exhaustiveProjectionPredicate `shouldBe` False
verifyPredicate exhaustiveProjectionPredicate `shouldReturn` UnverifiedOpaque
isNothing (symSatExt exhaustiveProjectionPredicate) `shouldBe` True
it "keeps one-sided emptiness proofs for repeated projection reads" $ do
predicateTranslationExact repeatedProjectionContradiction `shouldBe` False
verifyPredicate repeatedProjectionContradiction `shouldReturn` UnverifiedOpaque
symIsBot repeatedProjectionContradiction `shouldBe` True
it "keeps ordinary register witness extraction" $ do
let predicate = TReg boolOwnerIx .== TLit True :: HsPred BoolOwnerRegs ()
case symSatExt predicate of
Nothing -> expectationFailure "ordinary Bool register predicate lost its witness"
Just (registers, command) ->
evalPred predicate registers command `shouldBe` True
it "returns no witness instead of throwing on an unguarded input projection" $ do
let predicate =
PAnd
(inpProj boolKeyW withOwnerCtor #owner .== TLit "invented")
(PInCtor withoutOwnerCtor) ::
HsPred '[] BoolProjectionCmd
isNothing (symSatExt predicate) `shouldBe` True
it "proves the excluded third key unsatisfiable for an exact finite image" $ do
predicateTranslationReport exactExhaustiveProjectionPredicate
`shouldBe` ExactTranslation
verifyPredicate exactExhaustiveProjectionPredicate
`shouldReturn` VerifiedUnsatisfiable
symIsBot exactExhaustiveProjectionPredicate `shouldBe` True
it "returns a typed checked projection model" $ do
detail <- verifyPredicateDetailed exactEnabledProjectionPredicate
case detail of
PredicateSatisfiable ExactTranslation [projectionModel] -> do
projectionModelKeyAs @Text projectionModel `shouldBe` Just "enabled"
projectionModelOwnerAs @Bool projectionModel `shouldBe` Just True
other -> expectationFailure ("expected one exact projection model, got " <> show other)
verifyPredicate exactEnabledProjectionPredicate
`shouldReturn` VerifiedSatisfiable
it "uses a relation-safe projection owner in full witness extraction" $
case symSatExt exactEnabledProjectionPredicate of
Nothing -> expectationFailure "expected exact projection owner override"
Just (registers, ()) -> registers ! boolOwnerIx `shouldBe` True
it "installs a relation-safe owner into the matching input constructor field" $ do
let predicate =
PAnd
(PInCtor withOwnerCtor)
(inpProj exactBoolKeyW withOwnerCtor #owner .== TLit "enabled") ::
HsPred '[] BoolProjectionCmd
case symSatExt predicate of
Just (RNil, WithOwner True) -> pure ()
other -> expectationFailure ("expected WithOwner True, got " <> show (snd <$> other))
it "keeps path-local models distinct from an inconsistent full owner witness" $ do
let predicate =
PAnd
(regProj exactBoolKeyW boolOwnerIx .== TLit "enabled")
(regProj exactBoolAsIntW boolOwnerIx .== TLit 0) ::
HsPred BoolOwnerRegs ()
detail <- verifyPredicateDetailed predicate
case detail of
PredicateSatisfiable (ConservativeOverApproximation _) projectionModels ->
length projectionModels `shouldBe` 2
other -> expectationFailure ("expected conservative path models, got " <> show other)
isNothing (symSatExt predicate) `shouldBe` True
it "keeps equal display names distinct by structural position in models" $ do
let first = ZIdx :: Index '[ '("owner", Bool), '("owner", Bool)] Bool
second = SIdx ZIdx :: Index '[ '("owner", Bool), '("owner", Bool)] Bool
predicate =
PAnd
(regProj exactBoolKeyW first .== TLit "disabled")
(regProj exactBoolKeyW second .== TLit "enabled") ::
HsPred '[ '("owner", Bool), '("owner", Bool)] ()
detail <- verifyPredicateDetailed predicate
case detail of
PredicateSatisfiable ExactTranslation projectionModels -> do
length projectionModels `shouldBe` 2
fmap (projectionBasePosition . projectionDescriptorBase . projectionModelDescriptor) projectionModels
`shouldBe` [0, 1]
other -> expectationFailure ("expected two structural models, got " <> show other)
it "reports an inverse that rejects an admitted model as a contract violation" $ do
let witness = exactFieldWitness @BrokenRejectInverse
predicate =
regProj witness boolOwnerIx .== TLit "enabled" ::
HsPred BoolOwnerRegs ()
detail <- verifyPredicateDetailed predicate
case detail of
PredicateProjectionContractViolation ExactTranslation _ message ->
message `shouldContain` show ProjectionInverseRejectedDomainKey
other -> expectationFailure ("expected inverse rejection, got " <> show other)
compatibility <- verifyPredicate predicate
compatibility `shouldSatisfy` \case
UnverifiedSolverFailure _ -> True
_ -> False
it "reports an inverse getter mismatch as a contract violation" $ do
let witness = exactFieldWitness @BrokenRoundTripInverse
predicate =
regProj witness boolOwnerIx .== TLit "enabled" ::
HsPred BoolOwnerRegs ()
detail <- verifyPredicateDetailed predicate
case detail of
PredicateProjectionContractViolation ExactTranslation _ message ->
message `shouldContain` show ProjectionInverseRoundTripMismatch
other -> expectationFailure ("expected round-trip violation, got " <> show other)
it "catches an under-declared image only through the owner-side law" $ do
let witness = exactFieldWitness @UnderDeclaredDomain
checkFieldProjectionOwner witness True
`shouldBe` Left ProjectedKeyOutsideDeclaredDomain
describe "predicate-wide projection translation report" $ do
it "allows repeated exact reads and the same tag on distinct bases" $ do
let repeated =
regProj exactBoolKeyW boolOwnerIx
.== regProj exactBoolKeyW boolOwnerIx
distinctBases =
PAnd
(regProj exactRichFirstW richLeftIx .== TLit True)
(regProj exactRichFirstW richRightIx .== TLit False)
predicateTranslationReport repeated `shouldBe` ExactTranslation
predicateTranslationReport distinctBases `shouldBe` ExactTranslation
it "allows distinct exact tags only when their bases differ" $ do
let separate =
PAnd
(regProj exactRichFirstW richLeftIx .== TLit True)
(regProj exactRichSecondW richRightIx .== TLit False)
correlated =
PAnd
(regProj exactRichFirstW richLeftIx .== TLit True)
(regProj exactRichSecondW richLeftIx .== TLit False)
predicateTranslationReport separate `shouldBe` ExactTranslation
translationIssuesOf correlated `shouldSatisfy` any isConflictingViews
it "rejects a direct owner read combined with its projection" $ do
let predicate =
PAnd
(TReg boolOwnerIx .== TLit True)
(regProj exactBoolKeyW boolOwnerIx .== TLit "enabled")
translationIssuesOf predicate `shouldSatisfy` any isDirectAndProjected
it "constrains mixed exact/unconstrained occurrences in both orders but reports both inexact" $ do
let unconstrainedFirst =
PAnd
(regProj boolKeyW boolOwnerIx ./= TLit "disabled")
(regProj exactBoolKeyW boolOwnerIx ./= TLit "enabled")
exactFirst =
PAnd
(regProj exactBoolKeyW boolOwnerIx ./= TLit "enabled")
(regProj boolKeyW boolOwnerIx ./= TLit "disabled")
symIsBot unconstrainedFirst `shouldBe` True
symIsBot exactFirst `shouldBe` True
predicateTranslationExact unconstrainedFirst `shouldBe` False
predicateTranslationExact exactFirst `shouldBe` False
translationIssuesOf unconstrainedFirst `shouldSatisfy` any isConflictingViews
translationIssuesOf exactFirst `shouldSatisfy` any isConflictingViews
it "uses logical constructor domination independently of conjunction order" $ do
let projectionAtom =
inpProj exactBoolKeyW withOwnerCtor #owner .== TLit "enabled"
guardFirst = PAnd (PInCtor withOwnerCtor) projectionAtom
projectionFirst = PAnd projectionAtom (PInCtor withOwnerCtor)
nonDominating = POr (PInCtor withOwnerCtor) projectionAtom
predicateTranslationReport guardFirst `shouldBe` ExactTranslation
predicateTranslationReport projectionFirst `shouldBe` ExactTranslation
translationIssuesOf nonDominating
`shouldSatisfy` any isUnguardedProjection
it "keeps projection ordering and arithmetic conservative" $ do
let ordered =
PCmp
CmpLt
(regProj exactBoolAsIntW boolOwnerIx)
(TLit 1)
arithmetic =
tadd (regProj exactBoolAsIntW boolOwnerIx) (TLit 1)
.== TLit 2
translationIssuesOf ordered `shouldSatisfy` any isOutsideEquality
translationIssuesOf arithmetic `shouldSatisfy` any isOutsideEquality
describe "detailed projection analyses" $ do
it "retains one exact UNSAT result for a disjoint live pair" $ do
details <- checkTransitionDeterminismSymDetailed projectionDeterminismTransducer
case details of
[DeterminismAnalysisDetail _ _ (PredicateUnsatisfiable ExactTranslation)] -> pure ()
_ -> expectationFailure ("unexpected determinism details: " <> show (length details))
checkTransitionDeterminismSym projectionDeterminismTransducer `shouldBe` []
it "retains the exact dead-edge result used by the compatibility warning" $ do
details <- checkDeadEdgesSymDetailed projectionDeadEdgeTransducer
case details of
[DeadEdgeAnalysisDetail _ (PredicateUnsatisfiable ExactTranslation)] -> pure ()
_ -> expectationFailure ("unexpected dead-edge details: " <> show (length details))
length (checkDeadEdgesSym projectionDeadEdgeTransducer) `shouldBe` 1
describe "concrete-to-symbolic agreement" $ do
it "agrees for register projections in both truth directions" $
withMaxSuccess 25 $
property $ \rawHash same ->
let owner = DocInfo (T.pack rawHash) "title" []
comparison = if same then diHash owner else diHash owner <> "#different"
regs = RCons (Proxy @"doc") owner RNil
predicate = regProj docHashW docIx .== TLit comparison
concrete = evalPred predicate regs (NewDoc owner)
in ioProperty $
proveConcreteAgreement
predicate
(\env -> constrainFieldProjection env docHashW (PBReg docIx) (diHash owner))
concrete
it "agrees for input projections in both truth directions" $
withMaxSuccess 25 $
property $ \rawHash same ->
let owner = DocInfo (T.pack rawHash) "title" []
comparison = if same then diHash owner else diHash owner <> "#different"
input = NewDoc owner
predicate =
PAnd
(matchInCtor newDocCtor)
(inpProj docHashW newDocCtor #doc .== TLit comparison)
concrete = evalPred predicate RNil input
in ioProperty $
proveConcreteAgreement
predicate
( \env -> do
SBV.constrain (seInputCtor env SBV..== SBV.literal "NewDoc")
constrainFieldProjection
env
docHashW
(PBInp newDocCtor #doc)
(diHash owner)
)
concrete
describe "instance law harness" $ do
it "accepts the truthful generated-style witness" $
property $ \rawHash rawTitle ->
let owner = DocInfo (T.pack rawHash) (T.pack rawTitle) []
in fieldWitnessAgrees docHashW diHash owner
it "finds a deliberately wrong coherent instance" $
expectFailure $
property $ \value ->
fieldWitnessAgrees
wrongFirstW
(\(PairInts first _) -> first)
(PairInts value (value + 1))
describe "validation and replay" $ do
it "validates the projection-guarded transducer under default options" $
validateTransducer defaultValidationOptions docProjectionTransducer
`shouldBe` []
it "replays a projection-selected event to the complete forward state" $ do
let nextDoc = DocInfo "new-hash" "new title" [3]
case stepEither
docProjectionTransducer
(initial docProjectionTransducer, initialRegs docProjectionTransducer)
(NewDoc nextDoc) of
Left failure -> expectationFailure ("forward step failed: " <> show failure)
Right (forwardState, forwardRegs, events) ->
case reconstituteEither docProjectionTransducer events of
Left failure -> expectationFailure ("replay failed: " <> show failure)
Right (replayState, replayRegs) -> do
replayState `shouldBe` forwardState
replayRegs ! docIx `shouldBe` forwardRegs ! docIx