File size: 14,371 Bytes
a10b189 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 | {-# LANGUAGE DeriveEq #-}
{-# LANGUAGE DeriveShow #-}
{-# LANGUAGE DeriveGeneric #-}
{-|
Module : Assertica.Core.Assertion
Description : Explicit assertion system for mathematical obligations
Copyright : (c) 2026 Ahmad Ali Parr
License : BSL-1.0
The assertion system represents mathematical obligations as first-class program entities.
An assertion is an explicit proof goal that must be discharged by the proof-term checker.
Key design principles:
1. Explicit representation: Every assertion is a data structure, not a string or comment.
2. Fail-closed: An unknown or malformed assertion is UNPROVEN, not silently accepted.
3. Proof obligation boundary: The assertion clearly identifies what must be proven.
4. Composable propositions: Build complex propositions from simpler ones.
5. No hidden theorem proving: Assertions state obligations; the proof checker discharges them.
Integration:
- Input: Elaborator (Agent 3A) produces surface assertions
- Processing: This module represents obligations
- Output: Proof-term checker (Agent 2B) discharges proof obligations
-}
module Assertica.Core.Assertion
( -- * Core types
Assertion(..)
, ProofObligation(..)
, ObligationState(..)
, ObligationStore
-- * Assertion construction and querying
, mkAssertion
, assertionName
, assertionProp
, assertionLoc
, getObligations
, lookupObligation
, countByState
-- * Proposition utilities
, propFreeVariables
, propBoundVariables
, propSubstitute
, propAlphaRename
, prettyPrintProposition
-- * Obligation tracking
, emptyStore
, insertObligation
, updateObligation
, markProven
, markUnproven
, markRejected
, allProven
, unprovenObligations
-- * Elaboration interface
, SurfaceAssertion(..)
, elaborateAssertion
-- * Re-exports from AST
, Proposition(..)
, Term(..)
, Var(..)
, SourceLoc(..)
) where
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Data.Typeable (Typeable)
import GHC.Generics (Generic)
import Data.List (intercalate)
import Assertica.Core.AST
( Proposition(..)
, Term(..)
, Var(..)
, SourceLoc(..)
, freeVars
, substitute
, alphaRename
)
-- ============================================================================
-- Core Assertion Type
-- ============================================================================
-- | An assertion is a named proof obligation with a source location
data Assertion = Assertion
{ assertionName :: String
-- ^ Unique name for the assertion
, assertionProp :: Proposition
-- ^ The proposition to be proven
, assertionLoc :: SourceLoc
-- ^ Source location for error reporting
}
deriving (Eq, Show, Generic, Typeable)
-- | Proof obligation state tracking
data ObligationState
= Asserted -- ^ The obligation has been stated but not yet proven
| Proven -- ^ A valid proof has been accepted
| Unproven -- ^ The proof attempt was invalid or no proof provided
| Rejected -- ^ The obligation was explicitly rejected
deriving (Eq, Show, Ord, Generic, Typeable)
-- | A proof obligation with its current state
data ProofObligation = ProofObligation
{ obligationAssertion :: Assertion
, obligationState :: ObligationState
, obligationProofTerm :: Maybe String
-- ^ Optional reference to the proof term that discharges this obligation
}
deriving (Eq, Show, Generic, Typeable)
-- | Storage for proof obligations
-- Maps assertion names to their obligations
type ObligationStore = Map.Map String ProofObligation
-- ============================================================================
-- Assertion Construction
-- ============================================================================
-- | Create an assertion with default Asserted state
mkAssertion :: String -> Proposition -> SourceLoc -> Assertion
mkAssertion name prop loc = Assertion name prop loc
-- ============================================================================
-- Obligation Store Operations
-- ============================================================================
-- | Create an empty obligation store
emptyStore :: ObligationStore
emptyStore = Map.empty
-- | Insert a new obligation into the store
insertObligation :: Assertion -> ObligationStore -> ObligationStore
insertObligation assertion store =
let obligation = ProofObligation assertion Asserted Nothing
in Map.insert (assertionName assertion) obligation store
-- | Look up an obligation by assertion name
lookupObligation :: String -> ObligationStore -> Maybe ProofObligation
lookupObligation = Map.lookup
-- | Get all obligations from the store
getObligations :: ObligationStore -> [ProofObligation]
getObligations = Map.elems
-- | Update an obligation's state
updateObligation :: String -> ObligationState -> Maybe String -> ObligationStore -> Either String ObligationStore
updateObligation name state proofTerm store =
case Map.lookup name store of
Nothing -> Left $ "Obligation not found: " ++ name
Just obligation ->
let updated = obligation { obligationState = state, obligationProofTerm = proofTerm }
in Right $ Map.insert name updated store
-- | Mark an obligation as proven
markProven :: String -> Maybe String -> ObligationStore -> Either String ObligationStore
markProven name proofTerm = updateObligation name Proven proofTerm
-- | Mark an obligation as unproven
markUnproven :: String -> ObligationStore -> Either String ObligationStore
markUnproven name store = updateObligation name Unproven Nothing store
-- | Mark an obligation as rejected
markRejected :: String -> ObligationStore -> Either String ObligationStore
markRejected name store = updateObligation name Rejected Nothing store
-- | Check if all obligations are proven
allProven :: ObligationStore -> Bool
allProven store = all (\ob -> obligationState ob == Proven) (getObligations store)
-- | Get all unproven obligations
unprovenObligations :: ObligationStore -> [ProofObligation]
unprovenObligations store =
filter (\ob -> obligationState ob /= Proven) (getObligations store)
-- | Count obligations by state
countByState :: ObligationState -> ObligationStore -> Int
countByState state store =
length $ filter (\ob -> obligationState ob == state) (getObligations store)
-- ============================================================================
-- Proposition Utilities
-- ============================================================================
-- | Free variables in a proposition
propFreeVariables :: Proposition -> Set.Set Var
propFreeVariables (EqualityProp t1 t2) = Set.union (freeVars t1) (freeVars t2)
propFreeVariables (UniversalQuantProp v prop) =
Set.delete v (propFreeVariables prop)
propFreeVariables (ImplicationProp p q) =
Set.union (propFreeVariables p) (propFreeVariables q)
propFreeVariables (ConjunctionProp p q) =
Set.union (propFreeVariables p) (propFreeVariables q)
propFreeVariables (TypingProp t _) = freeVars t
propFreeVariables (PredicateApp _ terms) =
Set.unions $ map freeVars terms
propFreeVariables (NegationProp p) = propFreeVariables p
propFreeVariables (DisjunctionProp p q) =
Set.union (propFreeVariables p) (propFreeVariables q)
-- | Bound variables in a proposition
propBoundVariables :: Proposition -> Set.Set Var
propBoundVariables (EqualityProp t1 t2) = Set.empty -- Terms have their own binding
propBoundVariables (UniversalQuantProp v prop) =
Set.insert v (propBoundVariables prop)
propBoundVariables (ImplicationProp p q) =
Set.union (propBoundVariables p) (propBoundVariables q)
propBoundVariables (ConjunctionProp p q) =
Set.union (propBoundVariables p) (propBoundVariables q)
propBoundVariables (TypingProp _ _) = Set.empty
propBoundVariables (PredicateApp _ _) = Set.empty
propBoundVariables (NegationProp p) = propBoundVariables p
propBoundVariables (DisjunctionProp p q) =
Set.union (propBoundVariables p) (propBoundVariables q)
-- | Substitute a variable in a proposition
-- Must handle binding correctly to avoid variable capture
propSubstitute :: Var -> Term -> Proposition -> Proposition
propSubstitute var replacement prop = go prop
where
go (EqualityProp t1 t2) =
EqualityProp (substitute var replacement t1) (substitute var replacement t2)
go (UniversalQuantProp v p)
| v == var = UniversalQuantProp v p -- Bound variable shadows the substitution
| v `Set.member` freeVars replacement =
-- Need to rename v to avoid capture
let freshVar = freshenPropVar v prop replacement
in UniversalQuantProp freshVar (propSubstitute var replacement (propAlphaRename v freshVar p))
| otherwise = UniversalQuantProp v (go p)
go (ImplicationProp p q) =
ImplicationProp (go p) (go q)
go (ConjunctionProp p q) =
ConjunctionProp (go p) (go q)
go (TypingProp t ty) =
TypingProp (substitute var replacement t) ty
go (PredicateApp name terms) =
PredicateApp name (map (substitute var replacement) terms)
go (NegationProp p) =
NegationProp (go p)
go (DisjunctionProp p q) =
DisjunctionProp (go p) (go q)
-- | Alpha-rename a variable in a proposition
propAlphaRename :: Var -> Var -> Proposition -> Proposition
propAlphaRename oldVar newVar = go
where
go (EqualityProp t1 t2) =
EqualityProp (alphaRename oldVar newVar t1) (alphaRename oldVar newVar t2)
go (UniversalQuantProp v p)
| v == oldVar = UniversalQuantProp newVar (go p)
| otherwise = UniversalQuantProp v (go p)
go (ImplicationProp p q) = ImplicationProp (go p) (go q)
go (ConjunctionProp p q) = ConjunctionProp (go p) (go q)
go (TypingProp t ty) = TypingProp (alphaRename oldVar newVar t) ty
go (PredicateApp name terms) =
PredicateApp name (map (alphaRename oldVar newVar) terms)
go (NegationProp p) = NegationProp (go p)
go (DisjunctionProp p q) = DisjunctionProp (go p) (go q)
-- | Generate a fresh variable for propositions
freshenPropVar :: Var -> Proposition -> Term -> Var
freshenPropVar v prop replacement =
let occupied = Set.union (propFreeVariables prop) (freeVars replacement)
Var varName = v
candidates = map (\n -> Var (varName ++ "_" ++ show n)) [1..]
in head [c | c <- candidates, c `Set.notMember` occupied]
-- ============================================================================
-- Pretty Printing
-- ============================================================================
-- | Pretty-print a proposition for human-readable output
prettyPrintProposition :: Proposition -> String
prettyPrintProposition (EqualityProp t1 t2) =
prettyTerm t1 ++ " ≡ " ++ prettyTerm t2
prettyPrintProposition (UniversalQuantProp v prop) =
"∀ " ++ prettyVar v ++ ". " ++ prettyPrintProposition prop
prettyPrintProposition (ImplicationProp p q) =
"(" ++ prettyPrintProposition p ++ " → " ++ prettyPrintProposition q ++ ")"
prettyPrintProposition (ConjunctionProp p q) =
"(" ++ prettyPrintProposition p ++ " ∧ " ++ prettyPrintProposition q ++ ")"
prettyPrintProposition (TypingProp t ty) =
prettyTerm t ++ " : " ++ ty
prettyPrintProposition (PredicateApp name terms) =
name ++ "(" ++ intercalate ", " (map prettyTerm terms) ++ ")"
prettyPrintProposition (NegationProp p) =
"¬(" ++ prettyPrintProposition p ++ ")"
prettyPrintProposition (DisjunctionProp p q) =
"(" ++ prettyPrintProposition p ++ " ∨ " ++ prettyPrintProposition q ++ ")"
-- | Pretty-print a term (minimal)
prettyTerm :: Term -> String
prettyTerm (TVar v) = prettyVar v
prettyTerm (TAbs v body) = "(λ" ++ prettyVar v ++ ". " ++ prettyTerm body ++ ")"
prettyTerm (TApp f x) = "(" ++ prettyTerm f ++ " " ++ prettyTerm x ++ ")"
prettyTerm (TConst c) = c
prettyTerm (TLet v val body) = "(let " ++ prettyVar v ++ " = " ++ prettyTerm val ++ " in " ++ prettyTerm body ++ ")"
prettyTerm (TPair a b) = "(" ++ prettyTerm a ++ ", " ++ prettyTerm b ++ ")"
prettyTerm (TFst t) = "fst(" ++ prettyTerm t ++ ")"
prettyTerm (TSnd t) = "snd(" ++ prettyTerm t ++ ")"
-- | Pretty-print a variable
prettyVar :: Var -> String
prettyVar (Var name) = name
-- ============================================================================
-- Elaboration Interface
-- ============================================================================
-- | Surface-level assertion (produced by elaborator, Agent 3A)
-- This is the input type for elaboration
data SurfaceAssertion = SurfaceAssertion
{ surfaceName :: String
-- ^ The assertion name
, surfacePropositionText :: String
-- ^ The proposition as text (needs parsing by Agent 3A)
, surfaceSourceLoc :: SourceLoc
-- ^ Source location
}
deriving (Eq, Show, Generic, Typeable)
-- | Elaboration interface
-- This signature shows how surface assertions are converted to core assertions
-- Implementation of this function is the responsibility of Agent 3A
-- when they implement the parser/elaborator
--
-- Type signature for Agent 3A to implement:
-- elaborateSurfaceAssertion :: SurfaceAssertion -> Either String Assertion
--
-- For now, we provide a stub that always fails with an appropriate message
elaborateAssertion :: SurfaceAssertion -> Either String Assertion
elaborateAssertion surface =
Left $ "Elaboration not yet implemented. "
++ "Agent 3A must implement parsing of: "
++ surfacePropositionText surface
-- ============================================================================
-- Integration Interface for Agent 2B (Proof-Term Checker)
-- ============================================================================
{-|
Proof-term checker integration (Agent 2B)
The proof checker receives:
1. A Proposition (the obligation to discharge)
2. A ProofTerm (the evidence for the proposition)
Expected type signature in Agent 2B's module:
proofChecks :: ProofTerm -> Proposition -> Either String ()
Invariants the proof checker assumes about well-formed propositions:
- All bound variables are properly scoped (no captures)
- Free variables are explicitly mentioned in universal quantifications
- Terms in equality propositions are well-typed (type checking by Agent 3B)
- Predicate applications reference known predicates
- No circular references in implication chains
When a proof is invalid or the proposition is malformed, the checker
MUST return a descriptive error message, never silently reject.
-}
|