diff options
| author | adelon <22380201+adelon@users.noreply.github.com> | 2026-08-06 17:54:00 +0200 |
|---|---|---|
| committer | adelon <22380201+adelon@users.noreply.github.com> | 2026-08-06 17:54:00 +0200 |
| commit | 82328890108bae64b372b8d58620ebc62699de76 (patch) | |
| tree | 575404c6b425c19259c0ded296f1c8ffb7ff0e2b /source/Felix/Checking/Authority.hs | |
| parent | 1a25421c2a168d420581358c8733fcd8f36f379b (diff) | |
Diffstat (limited to 'source/Felix/Checking/Authority.hs')
| -rw-r--r-- | source/Felix/Checking/Authority.hs | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/source/Felix/Checking/Authority.hs b/source/Felix/Checking/Authority.hs new file mode 100644 index 0000000..58c123f --- /dev/null +++ b/source/Felix/Checking/Authority.hs @@ -0,0 +1,612 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- | Compact public fact authority and exact contextual authorization. +module Felix.Checking.Authority + ( EscapeKind(..) + , EscapeKinds + , emptyEscapeKinds + , singletonEscapeKind + , escapeKinds + , escapeKindsToList + , unionEscapeKinds + , AuthoritySafety + , cleanAuthoritySafety + , authoritySafety + , authoritySafetyEscapeKinds + , unionAuthoritySafety + , FactAuthority + , factAuthority + , factAuthorityTheorem + , factAuthoritySafety + , PreparedRequestDialect(..) + , PreparedRequestMode(..) + , PreparedRequestId + , preparedRequestId + , GuardedRuleSet + , guardedRuleSet + , guardedRuleTags + , KernelConstructionDescriptor(..) + , DatatypeCompilationDescriptor + , datatypeCompilationDescriptor + , CompilationDescriptor(..) + , DirectAuthorization(..) + , ValidationCertificate + , validationCertificate + , validationTarget + , validationDirectAuthorization + , ValidationCertificateError(..) + , CandidateSafety + , initialCandidateSafety + , candidateSafetyAuthority + , candidateFactAuthority + , accumulateFactSafety + , addCandidateEscape + , FactSafetyError(..) + , putEscapeKindsCache + , getEscapeKindsCache + , putAuthoritySafetyCache + , getAuthoritySafetyCache + , putFactAuthorityCache + , getFactAuthorityCache + , putPreparedRequestIdCache + , getPreparedRequestIdCache + , putDirectAuthorizationCache + , getDirectAuthorizationCache + , putValidationCertificateCache + , getValidationCertificateCache + ) where + +import Base +import Felix.Checking.Foundation +import Felix.Checking.Identity +import Felix.Cache.Codec + +import Control.DeepSeq (NFData) +import Control.Monad (unless, when) +import Data.Bits ((.&.), (.|.), bit, complement) +import Data.ByteString (ByteString) +import Data.ByteString qualified as ByteString +import Data.List (filter) +import Data.List.NonEmpty qualified as NonEmpty +import Data.Set qualified as Set +import Data.Word (Word8) + + +data EscapeKind + = SourceAxiom + | Omitted + deriving stock (Show, Eq, Ord, Enum, Bounded, Generic) + deriving anyclass (NFData) + +-- | Canonical bounded set. The bit positions are stable cache tags. +newtype EscapeKinds = + EscapeKinds Word8 + deriving stock (Show, Eq, Ord, Generic) + deriving newtype (NFData) + +emptyEscapeKinds :: EscapeKinds +emptyEscapeKinds = + EscapeKinds 0 + +singletonEscapeKind :: EscapeKind -> EscapeKinds +singletonEscapeKind = + EscapeKinds . escapeKindBit + +escapeKinds :: Foldable collection => collection EscapeKind -> EscapeKinds +escapeKinds = + foldl' + (\acc kind -> + unionEscapeKinds acc (singletonEscapeKind kind)) + emptyEscapeKinds + +escapeKindsToList :: EscapeKinds -> [EscapeKind] +escapeKindsToList kinds = + filter + (\kind -> + let EscapeKinds bits = kinds + in bits .&. escapeKindBit kind /= 0) + [SourceAxiom, Omitted] + +unionEscapeKinds :: EscapeKinds -> EscapeKinds -> EscapeKinds +unionEscapeKinds (EscapeKinds left) (EscapeKinds right) = + EscapeKinds (left .|. right) + +escapeKindBit :: EscapeKind -> Word8 +escapeKindBit = \case + SourceAxiom -> bit 0 + Omitted -> bit 1 + +allEscapeBits :: Word8 +allEscapeBits = + escapeKindBit SourceAxiom .|. escapeKindBit Omitted + + +data AuthoritySafety + = Clean + | EscapeHatchBacked !EscapeKinds + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData) + +cleanAuthoritySafety :: AuthoritySafety +cleanAuthoritySafety = + Clean + +authoritySafety :: EscapeKinds -> AuthoritySafety +authoritySafety kinds + | kinds == emptyEscapeKinds = Clean + | otherwise = EscapeHatchBacked kinds + +authoritySafetyEscapeKinds :: AuthoritySafety -> EscapeKinds +authoritySafetyEscapeKinds = \case + Clean -> emptyEscapeKinds + EscapeHatchBacked kinds -> kinds + +unionAuthoritySafety + :: AuthoritySafety + -> AuthoritySafety + -> AuthoritySafety +unionAuthoritySafety left right = + authoritySafety + (unionEscapeKinds + (authoritySafetyEscapeKinds left) + (authoritySafetyEscapeKinds right)) + + +data FactAuthority = FactAuthority + !TheoremRef + !AuthoritySafety + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData) + +factAuthority :: TheoremRef -> AuthoritySafety -> FactAuthority +factAuthority = + FactAuthority + +factAuthorityTheorem :: FactAuthority -> TheoremRef +factAuthorityTheorem (FactAuthority reference _) = + reference + +factAuthoritySafety :: FactAuthority -> AuthoritySafety +factAuthoritySafety (FactAuthority _ safety) = + safety + + +data PreparedRequestDialect + = PreparedRequestFof + | PreparedRequestTh0 + deriving stock (Show, Eq, Ord, Generic) + +data PreparedRequestMode + = PreparedRequestDirect + | PreparedRequestIndirect + deriving stock (Show, Eq, Ord, Generic) + +newtype PreparedRequestId = + PreparedRequestId CacheDigest + deriving stock (Show, Eq, Ord, Generic) + deriving newtype (Hashable, NFData) + +preparedRequestId + :: PreparedRequestDialect + -> PreparedRequestMode + -> ByteString + -> PreparedRequestId +preparedRequestId dialect mode bytes = + PreparedRequestId + (hashCacheFields + "felix-prepared-request-v2" + [ ByteString.singleton (preparedRequestDialectTag dialect) + , ByteString.singleton (preparedRequestModeTag mode) + , bytes + ]) + +preparedRequestDialectTag :: PreparedRequestDialect -> Word8 +preparedRequestDialectTag = \case + PreparedRequestFof -> 0x00 + PreparedRequestTh0 -> 0x01 + +preparedRequestModeTag :: PreparedRequestMode -> Word8 +preparedRequestModeTag = \case + PreparedRequestDirect -> 0x00 + PreparedRequestIndirect -> 0x01 + + +-- | A nonempty canonical set of guarded kernel rules. +newtype GuardedRuleSet = + GuardedRuleSet (Set KernelRuleTag) + deriving stock (Show, Eq, Ord, Generic) + +guardedRuleSet :: NonEmpty KernelRuleTag -> GuardedRuleSet +guardedRuleSet = + GuardedRuleSet . Set.fromList . NonEmpty.toList + +guardedRuleTags :: GuardedRuleSet -> Set KernelRuleTag +guardedRuleTags (GuardedRuleSet rules) = + rules + +data KernelConstructionDescriptor + = FoundationLeaf !FoundationAxiomTag + | GuardedFoundationRules !GuardedRuleSet + | CheckedDefinitionEquation !ObjectId + | CheckedSetConstructionExtensionality !ObjectId !CacheDigest + deriving stock (Show, Eq, Ord, Generic) + +-- | Exact semantic members of one trusted datatype compilation. +data DatatypeCompilationDescriptor = + DatatypeCompilationDescriptor + !ObjectId + !(NonEmpty ObjectId) + ![TheoremRef] + deriving stock (Show, Eq, Ord, Generic) + +datatypeCompilationDescriptor + :: ObjectId + -> NonEmpty ObjectId + -> [TheoremRef] + -> DatatypeCompilationDescriptor +datatypeCompilationDescriptor = + DatatypeCompilationDescriptor + +data CompilationDescriptor + = DatatypeCompilation !DatatypeCompilationDescriptor + deriving stock (Show, Eq, Ord, Generic) + +data DirectAuthorization + = CheckedKernelConstruction !KernelConstructionDescriptor + | CheckedSourceProof ![PreparedRequestId] + | TrustedCompilation !CompilationDescriptor + | SourceAxiomAuthorization + | OmittedAuthorization + deriving stock (Show, Eq, Ord, Generic) + + +data ValidationCertificate = ValidationCertificate + !FactAuthority + !DirectAuthorization + deriving stock (Show, Eq, Ord, Generic) + +data ValidationCertificateError + = SourceAxiomSafetyMismatch !AuthoritySafety + | OmittedSafetyMissing !AuthoritySafety + deriving stock (Show, Eq) + +validationCertificate + :: FactAuthority + -> DirectAuthorization + -> Either ValidationCertificateError ValidationCertificate +validationCertificate target direct = do + case direct of + SourceAxiomAuthorization -> + unless + (factAuthoritySafety target + == authoritySafety + (singletonEscapeKind SourceAxiom)) + (Left + (SourceAxiomSafetyMismatch + (factAuthoritySafety target))) + OmittedAuthorization -> + unless + (Omitted + `elem` escapeKindsToList + (authoritySafetyEscapeKinds + (factAuthoritySafety target))) + (Left + (OmittedSafetyMissing + (factAuthoritySafety target))) + CheckedKernelConstruction{} -> pure () + CheckedSourceProof{} -> pure () + TrustedCompilation{} -> pure () + pure (ValidationCertificate target direct) + +validationTarget :: ValidationCertificate -> FactAuthority +validationTarget (ValidationCertificate target _) = + target + +validationDirectAuthorization + :: ValidationCertificate + -> DirectAuthorization +validationDirectAuthorization (ValidationCertificate _ direct) = + direct + + +newtype CandidateSafety = + CandidateSafety AuthoritySafety + deriving stock (Show, Eq, Ord, Generic) + deriving newtype (NFData) + +initialCandidateSafety :: CandidateSafety +initialCandidateSafety = + CandidateSafety Clean + +candidateSafetyAuthority :: CandidateSafety -> AuthoritySafety +candidateSafetyAuthority (CandidateSafety safety) = + safety + +-- | Freeze the final safety of one completed candidate into public authority. +-- +-- The trusted completion boundary uses this projection for both the inert +-- validation certificate and its runtime pending authorization. +candidateFactAuthority + :: TheoremRef + -> CandidateSafety + -> FactAuthority +candidateFactAuthority reference (CandidateSafety safety) = + FactAuthority reference safety + +data FactSafetyError + = FactSafetyTheoremMismatch !TheoremRef !TheoremRef + deriving stock (Show, Eq) + +-- | Check theorem agreement and accumulate already-authorized public safety. +-- +-- This operation does not authorize a freely constructed 'FactAuthority'. +-- Callers may use it only after checking opaque global, imported, or staged +-- builder authorization at the boundary that owns that authority. +accumulateFactSafety + :: TheoremRef + -> FactAuthority + -> CandidateSafety + -> Either FactSafetyError CandidateSafety +accumulateFactSafety expected supplied (CandidateSafety current) = do + unless + (factAuthorityTheorem supplied == expected) + (Left + (FactSafetyTheoremMismatch + expected + (factAuthorityTheorem supplied))) + pure + (CandidateSafety + (unionAuthoritySafety + current + (factAuthoritySafety supplied))) + +addCandidateEscape + :: EscapeKind + -> CandidateSafety + -> CandidateSafety +addCandidateEscape kind (CandidateSafety current) = + CandidateSafety + (unionAuthoritySafety + current + (authoritySafety (singletonEscapeKind kind))) + + +putEscapeKindsCache :: EscapeKinds -> CachePut +putEscapeKindsCache (EscapeKinds bits) = + putCacheTag bits + +getEscapeKindsCache :: CacheGet EscapeKinds +getEscapeKindsCache = do + bits <- getCacheTag + unless + (bits .&. complement allEscapeBits == 0) + (fail "unknown cache escape-kind bit") + pure (EscapeKinds bits) + +putAuthoritySafetyCache :: AuthoritySafety -> CachePut +putAuthoritySafetyCache = \case + Clean -> + putCacheTag 0x00 + EscapeHatchBacked kinds -> do + putCacheTag 0x01 + putEscapeKindsCache kinds + +getAuthoritySafetyCache :: CacheGet AuthoritySafety +getAuthoritySafetyCache = + getCacheTag >>= \case + 0x00 -> + pure Clean + 0x01 -> do + kinds <- getEscapeKindsCache + when + (kinds == emptyEscapeKinds) + (fail "empty escape-hatch-backed safety") + pure (EscapeHatchBacked kinds) + tag -> + fail ("unknown cache authority-safety tag " <> show tag) + +putFactAuthorityCache :: FactAuthority -> CachePut +putFactAuthorityCache (FactAuthority reference safety) = do + putTheoremRefCache reference + putAuthoritySafetyCache safety + +getFactAuthorityCache :: CacheGet FactAuthority +getFactAuthorityCache = + FactAuthority + <$> getTheoremRefCache + <*> getAuthoritySafetyCache + +putPreparedRequestIdCache :: PreparedRequestId -> CachePut +putPreparedRequestIdCache (PreparedRequestId digest) = + putCacheDigest digest + +getPreparedRequestIdCache :: CacheGet PreparedRequestId +getPreparedRequestIdCache = + PreparedRequestId <$> getCacheDigest + +putDirectAuthorizationCache :: DirectAuthorization -> CachePut +putDirectAuthorizationCache = \case + CheckedKernelConstruction descriptor -> do + putCacheTag 0x00 + putKernelDescriptor descriptor + CheckedSourceProof requests -> do + putCacheTag 0x01 + putCacheList putPreparedRequestIdCache requests + TrustedCompilation descriptor -> do + putCacheTag 0x02 + putCompilationDescriptor descriptor + SourceAxiomAuthorization -> + putCacheTag 0x03 + OmittedAuthorization -> + putCacheTag 0x04 + +getDirectAuthorizationCache :: CacheGet DirectAuthorization +getDirectAuthorizationCache = + getCacheTag >>= \case + 0x00 -> + CheckedKernelConstruction <$> getKernelDescriptor + 0x01 -> + CheckedSourceProof + <$> getCacheList getPreparedRequestIdCache + 0x02 -> + TrustedCompilation <$> getCompilationDescriptor + 0x03 -> + pure SourceAxiomAuthorization + 0x04 -> + pure OmittedAuthorization + tag -> + fail ("unknown cache direct-authorization tag " <> show tag) + +putValidationCertificateCache :: ValidationCertificate -> CachePut +putValidationCertificateCache + (ValidationCertificate target direct) = do + putFactAuthorityCache target + putDirectAuthorizationCache direct + +getValidationCertificateCache :: CacheGet ValidationCertificate +getValidationCertificateCache = do + target <- getFactAuthorityCache + direct <- getDirectAuthorizationCache + case validationCertificate target direct of + Left err -> + fail ("invalid cache validation certificate: " <> show err) + Right certificate -> + pure certificate + + +putKernelDescriptor :: KernelConstructionDescriptor -> CachePut +putKernelDescriptor = \case + FoundationLeaf tag -> do + putCacheTag 0x00 + putFoundationTag tag + GuardedFoundationRules rules -> do + putCacheTag 0x01 + putCacheList putRuleTag + (Set.toAscList (guardedRuleTags rules)) + CheckedDefinitionEquation identity -> do + putCacheTag 0x02 + putObjectIdCache identity + CheckedSetConstructionExtensionality identity construction -> do + putCacheTag 0x03 + putObjectIdCache identity + putCacheDigest construction + +getKernelDescriptor :: CacheGet KernelConstructionDescriptor +getKernelDescriptor = + getCacheTag >>= \case + 0x00 -> FoundationLeaf <$> getFoundationTag + 0x01 -> do + tags <- getCacheList getRuleTag + rules <- + maybe + (fail "cache guarded-rule set is empty") + pure + (NonEmpty.nonEmpty tags) + unless (strictlyIncreasing tags) + (fail "cache guarded-rule tags are not in canonical order") + pure (GuardedFoundationRules (guardedRuleSet rules)) + 0x02 -> CheckedDefinitionEquation <$> getObjectIdCache + 0x03 -> + CheckedSetConstructionExtensionality + <$> getObjectIdCache + <*> getCacheDigest + tag -> + fail ("unknown cache kernel-construction tag " <> show tag) + +putCompilationDescriptor :: CompilationDescriptor -> CachePut +putCompilationDescriptor + (DatatypeCompilation + (DatatypeCompilationDescriptor + carrier constructors facts)) = do + putCacheTag 0x00 + putObjectIdCache carrier + putCacheList putObjectIdCache (NonEmpty.toList constructors) + putCacheList putTheoremRefCache facts + +getCompilationDescriptor :: CacheGet CompilationDescriptor +getCompilationDescriptor = + getCacheTag >>= \case + 0x00 -> do + carrier <- getObjectIdCache + rawConstructors <- + getCacheList getObjectIdCache + constructors <- + maybe + (fail "cache datatype compilation has no constructors") + pure + (NonEmpty.nonEmpty rawConstructors) + facts <- getCacheList getTheoremRefCache + pure + (DatatypeCompilation + (DatatypeCompilationDescriptor + carrier constructors facts)) + tag -> + fail ("unknown cache compilation tag " <> show tag) + +putFoundationTag :: FoundationAxiomTag -> CachePut +putFoundationTag = + putCacheTag . \case + EmptyCharacteristic -> 0x00 + PairSetCharacteristic -> 0x01 + FamilyUnionCharacteristic -> 0x02 + PowerSetCharacteristic -> 0x03 + SeparationCharacteristic -> 0x04 + ReplacementCharacteristic -> 0x05 + SetChooseWitness -> 0x06 + SetExtensionality -> 0x07 + SetInduction -> 0x08 + PropositionalExtensionality -> 0x09 + DoubleNegationElim -> 0x0a + UnivOfContains -> 0x0b + UnivOfTransitive -> 0x0c + UnivOfFamilyUnionClosed -> 0x0d + UnivOfPowerSetClosed -> 0x0e + UnivOfReplacementClosed -> 0x0f + UnivOfMinimal -> 0x10 + +getFoundationTag :: CacheGet FoundationAxiomTag +getFoundationTag = + getCacheTag >>= \case + 0x00 -> pure EmptyCharacteristic + 0x01 -> pure PairSetCharacteristic + 0x02 -> pure FamilyUnionCharacteristic + 0x03 -> pure PowerSetCharacteristic + 0x04 -> pure SeparationCharacteristic + 0x05 -> pure ReplacementCharacteristic + 0x06 -> pure SetChooseWitness + 0x07 -> pure SetExtensionality + 0x08 -> pure SetInduction + 0x09 -> pure PropositionalExtensionality + 0x0a -> pure DoubleNegationElim + 0x0b -> pure UnivOfContains + 0x0c -> pure UnivOfTransitive + 0x0d -> pure UnivOfFamilyUnionClosed + 0x0e -> pure UnivOfPowerSetClosed + 0x0f -> pure UnivOfReplacementClosed + 0x10 -> pure UnivOfMinimal + tag -> + fail ("unknown cache foundation-axiom tag " <> show tag) + +putRuleTag :: KernelRuleTag -> CachePut +putRuleTag = + putCacheTag . \case + SetLfpBound -> 0x00 + SetLfpLeast -> 0x01 + SetLfpFixed -> 0x02 + SetLfpInduct -> 0x03 + +getRuleTag :: CacheGet KernelRuleTag +getRuleTag = + getCacheTag >>= \case + 0x00 -> pure SetLfpBound + 0x01 -> pure SetLfpLeast + 0x02 -> pure SetLfpFixed + 0x03 -> pure SetLfpInduct + tag -> + fail ("unknown cache kernel-rule tag " <> show tag) + +strictlyIncreasing :: Ord value => [value] -> Bool +strictlyIncreasing values = + and (zipWith (<) values (drop 1 values)) |
