Download funtan/deed_validator.hs from Snapkitty/snapkitty-clojure-lisp-bridge: direct link, hf CLI and curl.
- Browser
- Download file 22.5 kB
-
https://huggingface.co/Snapkitty/snapkitty-clojure-lisp-bridge/resolve/main/funtan/deed_validator.hs
- Command line
-
hf download hf://Snapkitty/snapkitty-clojure-lisp-bridge/funtan/deed_validator.hs
-
curl -L -o deed_validator.hs https://huggingface.co/Snapkitty/snapkitty-clojure-lisp-bridge/resolve/main/funtan/deed_validator.hs
22.5 kB
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- SnapKitty Sovereign β Haskell Trust Deed Validator v2.0 | |
| -- LISP DSL β Haskell enforcement β Rust runtime | |
| -- | |
| -- Rules are loaded from deed-rules.lisp (Funtan DSL) at startup. | |
| -- Haskell enforces them. Rust calls this binary as a subprocess. | |
| -- | |
| -- LiquidHaskell refinement annotations are present throughout. | |
| -- Activate with: ghc -fplugin=LiquidHaskell (requires liquid-fixpoint + Z3) | |
| -- Without LH installed, annotations are comments β smart constructors | |
| -- provide equivalent runtime guarantees. | |
| -- | |
| -- stdin protocol (9 lines): | |
| -- 1: agent_id | |
| -- 2: allowed_actions (comma-separated) | |
| -- 3: restricted_actions (comma-separated) | |
| -- 4: trust_score (float) | |
| -- 5: provided_seal (64-char hex string) | |
| -- 6: expires_at (unix timestamp seconds, integer) | |
| -- 7: status (active|revoked|expired|pending-renewal) | |
| -- 8: escalation_authority (agent_id or "none") | |
| -- 9: spec_path (path to deed-rules.lisp, or "default") | |
| -- | |
| -- stdout: key=value pairs | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| module DeedValidator where | |
| import Data.Char (toLower, isHexDigit, isSpace, isDigit) | |
| import Data.List (intersect, nub, isPrefixOf, isSuffixOf) | |
| import Data.Maybe (mapMaybe, fromMaybe) | |
| import System.IO (hPutStrLn, stderr, hSetBuffering, stdout, BufferMode(..)) | |
| import Control.Exception (try, SomeException) | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- Minimal S-expression parser for deed-rules.lisp | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| data SExpr | |
| = Atom String | |
| | SList [SExpr] | |
| deriving (Show, Eq) | |
| parseSExprs :: String -> [SExpr] | |
| parseSExprs input = fst (parseMany (stripComments input)) | |
| where | |
| stripComments [] = [] | |
| stripComments (';':rest) = stripComments (dropWhile (/= '\n') rest) | |
| stripComments (c:cs) = c : stripComments cs | |
| parseMany :: String -> ([SExpr], String) | |
| parseMany s = | |
| let s' = dropWhile isSpace s | |
| in case s' of | |
| [] -> ([], []) | |
| (')':_) -> ([], s') | |
| _ -> let (e, rest) = parseOne s' | |
| (es, rem') = parseMany rest | |
| in (e:es, rem') | |
| parseOne :: String -> (SExpr, String) | |
| parseOne [] = (Atom "", []) | |
| parseOne ('(':rest) = | |
| let (children, after) = parseMany rest | |
| after' = case dropWhile isSpace after of | |
| (')':xs) -> xs | |
| xs -> xs | |
| in (SList children, after') | |
| parseOne ('"':rest) = | |
| let (str, after) = break (== '"') rest | |
| in (Atom str, drop 1 after) | |
| parseOne s = | |
| let (tok, rest) = break (\c -> isSpace c || c == '(' || c == ')') s | |
| in (Atom tok, rest) | |
| -- Extract a named list from a deed-spec s-expr | |
| lookupList :: String -> SExpr -> Maybe [SExpr] | |
| lookupList key (SList (Atom "deed-spec" : children)) = | |
| let find [] = Nothing | |
| find (SList (Atom k : vals) : rest) | |
| | k == key = Just vals | |
| | otherwise = find rest | |
| find (_ : rest) = find rest | |
| in find children | |
| lookupList key (SList children) = | |
| let find [] = Nothing | |
| find (SList (Atom k : vals) : rest) | |
| | k == key = Just vals | |
| | otherwise = find rest | |
| find (_ : rest) = find rest | |
| in find children | |
| lookupList _ _ = Nothing | |
| lookupAtom :: String -> SExpr -> Maybe String | |
| lookupAtom key spec = case lookupList key spec of | |
| Just (Atom v : _) -> Just v | |
| _ -> Nothing | |
| lookupFloat :: String -> SExpr -> Maybe Double | |
| lookupFloat key spec = case lookupAtom key spec of | |
| Just s -> case reads s of { [(v,"")] -> Just v; _ -> Nothing } | |
| Nothing -> Nothing | |
| lookupAtoms :: String -> SExpr -> [String] | |
| lookupAtoms key spec = case lookupList key spec of | |
| Just atoms -> [v | Atom v <- atoms] | |
| Nothing -> [] | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- DeedSpec β loaded from deed-rules.lisp | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| data DeedSpec = DeedSpec | |
| { specTrustMin :: Double -- default 0.01 | |
| , specTrustMax :: Double -- default 1.0 | |
| , specSealMinLen :: Int -- default 64 | |
| , specExpiryRequired :: Bool -- default True | |
| , specAuthorityModel :: String -- "role-based" | |
| , specAuthorityMinTrust :: Double -- default 1.0 | |
| , specStatusValues :: [String] -- valid status strings | |
| , specBlockedActions :: [String] -- globally forbidden | |
| , specSealMustCover :: [String] -- fields seal must cover | |
| } deriving (Show) | |
| defaultDeedSpec :: DeedSpec | |
| defaultDeedSpec = DeedSpec | |
| { specTrustMin = 0.01 | |
| , specTrustMax = 1.0 | |
| , specSealMinLen = 64 | |
| , specExpiryRequired = True | |
| , specAuthorityModel = "role-based" | |
| , specAuthorityMinTrust = 1.0 | |
| , specStatusValues = ["active", "expired", "revoked", "pending-renewal"] | |
| , specBlockedActions = ["delete_ledger","modify_deed","override_all", | |
| "jailbreak","ignore_previous"] | |
| , specSealMustCover = ["agent-id","trust-score","expires-at","status", | |
| "escalation-authority","allowed-actions","restricted-actions"] | |
| } | |
| loadDeedSpec :: FilePath -> IO DeedSpec | |
| loadDeedSpec path = do | |
| result <- try (readFile path) :: IO (Either SomeException String) | |
| case result of | |
| Left err -> do | |
| hPutStrLn stderr $ "[deed-validator] spec file not found: " ++ path | |
| ++ " β using defaults. Error: " ++ show err | |
| return defaultDeedSpec | |
| Right content -> do | |
| let exprs = parseSExprs content | |
| case exprs of | |
| (spec:_) -> do | |
| let ds = DeedSpec | |
| { specTrustMin = fromMaybe 0.01 (lookupFloat "trust-score-min" spec) | |
| , specTrustMax = fromMaybe 1.0 (lookupFloat "trust-score-max" spec) | |
| , specSealMinLen = fromMaybe 64 (fmap round (lookupFloat "seal-min-length" spec)) | |
| , specExpiryRequired = fromMaybe True (fmap (== "true") (lookupAtom "expiry-required" spec)) | |
| , specAuthorityModel = fromMaybe "role-based" (lookupAtom "authority-model" spec) | |
| , specAuthorityMinTrust = fromMaybe 1.0 (lookupFloat "authority-min-trust" spec) | |
| , specStatusValues = let vs = lookupAtoms "status-values" spec | |
| in if null vs then specStatusValues defaultDeedSpec else vs | |
| , specBlockedActions = let bs = lookupAtoms "globally-blocked-actions" spec | |
| in if null bs then specBlockedActions defaultDeedSpec else bs | |
| , specSealMustCover = let cs = lookupAtoms "seal-must-cover" spec | |
| in if null cs then specSealMustCover defaultDeedSpec else cs | |
| } | |
| hPutStrLn stderr $ "[deed-validator] loaded spec v" ++ | |
| fromMaybe "?" (lookupAtom "version" spec) | |
| return ds | |
| [] -> do | |
| hPutStrLn stderr "[deed-validator] empty spec file β using defaults" | |
| return defaultDeedSpec | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- TrustDeed β full field set (matches Rust struct) | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- {-@ type TrustScore = {v:Double | v >= 0.01 && v <= 1.0} @-} | |
| -- {-@ type ValidSeal = {v:String | len v >= 64} @-} | |
| -- {-@ type FutureTime = {v:Int | v > 0} @-} | |
| newtype TrustScore = TrustScore Double deriving (Show) | |
| newtype ValidSeal = ValidSeal String deriving (Show) | |
| -- Smart constructor β invalid trust scores cannot be constructed | |
| -- {-@ mkTrustScore :: DeedSpec -> Double -> Either String TrustScore @-} | |
| mkTrustScore :: DeedSpec -> Double -> Either String TrustScore | |
| mkTrustScore spec s | |
| | s < specTrustMin spec = Left $ "V3:TRUST_BELOW_MIN score=" ++ show s | |
| | s > specTrustMax spec = Left $ "V3:TRUST_ABOVE_MAX score=" ++ show s | |
| | otherwise = Right (TrustScore s) | |
| -- Smart constructor β short or non-hex seals cannot be constructed | |
| -- {-@ mkValidSeal :: DeedSpec -> String -> Either String ValidSeal @-} | |
| mkValidSeal :: DeedSpec -> String -> Either String ValidSeal | |
| mkValidSeal spec seal | |
| | null seal = Left "V4:NO_SEAL" | |
| | length seal < specSealMinLen spec = Left $ "V4:SEAL_TOO_SHORT len=" ++ show (length seal) | |
| | not (all isHexDigit seal) = Left "V4:SEAL_NOT_HEX" | |
| | otherwise = Right (ValidSeal seal) | |
| data TrustDeed = TrustDeed | |
| { deedAgent :: String | |
| , deedAllowed :: [String] | |
| , deedRestricted :: [String] | |
| , deedScore :: TrustScore | |
| , deedSeal :: ValidSeal | |
| , deedExpiresAt :: Integer -- unix timestamp | |
| , deedStatus :: String -- active|revoked|expired|pending-renewal | |
| , deedEscalation :: String -- agent_id or "none" | |
| } deriving (Show) | |
| newtype DeedToken = DeedToken { deedContent :: TrustDeed } | |
| mkDeedToken :: TrustDeed -> DeedToken | |
| mkDeedToken = DeedToken | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- Validation checks β each returns (pass/fail, message) | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- V1: Structure β required non-empty fields | |
| checkStructure :: TrustDeed -> (Bool, String) | |
| checkStructure d | |
| | null (deedAgent d) = (False, "V1:EMPTY_AGENT_ID") | |
| | null (deedAllowed d) = (False, "V1:NO_ALLOWED_ACTIONS β deed must permit at least one action") | |
| | otherwise = (True, "V1:STRUCTURAL_OK") | |
| -- V2: Disjoint sets β allowed β© restricted = β | |
| checkDisjoint :: TrustDeed -> (Bool, String) | |
| checkDisjoint d = | |
| let overlap = deedAllowed d `intersect` deedRestricted d | |
| in if null overlap | |
| then (True, "V2:SETS_DISJOINT") | |
| else (False, "V2:OVERLAP_DETECTED actions=" ++ show overlap) | |
| -- V3: Trust score β validated at construction time by mkTrustScore | |
| -- This check confirms the constructed score is in spec range | |
| checkTrustScore :: DeedSpec -> TrustDeed -> (Bool, String) | |
| checkTrustScore spec d = | |
| let TrustScore s = deedScore d | |
| in if s >= specTrustMin spec && s <= specTrustMax spec | |
| then (True, "V3:TRUST_IN_RANGE score=" ++ show s) | |
| else (False, "V3:TRUST_OUT_OF_RANGE score=" ++ show s) | |
| -- V4: Seal format β validated at construction time by mkValidSeal | |
| checkSealFormat :: DeedSpec -> TrustDeed -> (Bool, String) | |
| checkSealFormat spec d = | |
| let ValidSeal seal = deedSeal d | |
| in if length seal >= specSealMinLen spec && all isHexDigit seal | |
| then (True, "V4:SEAL_FORMAT_OK len=" ++ show (length seal)) | |
| else (False, "V4:SEAL_FORMAT_FAIL") | |
| -- V5: Authority model β role-based, NOT name-based | |
| -- Any agent with trust_score = 1.0 has authority. No names hardcoded. | |
| checkAuthorityModel :: DeedSpec -> TrustDeed -> (Bool, String) | |
| checkAuthorityModel spec d = | |
| let TrustScore s = deedScore d | |
| claimsAuth = s >= specAuthorityMinTrust spec | |
| model = specAuthorityModel spec | |
| in if claimsAuth && model /= "role-based" | |
| then (False, "V5:AUTHORITY_MODEL_MISCONFIGURED β spec requires role-based") | |
| else (True, "V5:AUTHORITY_MODEL_OK model=" ++ model | |
| ++ if claimsAuth then " (claims authority)" else "") | |
| -- V6: Expiration β deed must not be expired, status must be active | |
| checkExpiration :: DeedSpec -> TrustDeed -> Integer -> (Bool, String) | |
| checkExpiration spec d nowTs | |
| | specExpiryRequired spec && deedExpiresAt d <= 0 | |
| = (False, "V6:EXPIRES_AT_MISSING") | |
| | deedExpiresAt d > 0 && nowTs > deedExpiresAt d | |
| = (False, "V6:DEED_EXPIRED expires_at=" ++ show (deedExpiresAt d)) | |
| | deedStatus d `notElem` specStatusValues spec | |
| = (False, "V6:INVALID_STATUS status=" ++ deedStatus d) | |
| | deedStatus d /= "active" | |
| = (False, "V6:DEED_NOT_ACTIVE status=" ++ deedStatus d) | |
| | otherwise | |
| = (True, "V6:DEED_ACTIVE status=" ++ deedStatus d) | |
| -- V7: Seal coverage β verify the seal covers all integrity fields | |
| -- We cannot recompute SHA-256 without cryptonite, but we verify that the | |
| -- seal FIELD SET declared in the spec matches what Rust computes. | |
| -- Full content verification happens in Rust's verify_integrity(). | |
| checkSealCoverage :: DeedSpec -> TrustDeed -> (Bool, String) | |
| checkSealCoverage spec _d = | |
| let required = specSealMustCover spec | |
| covered = ["agent-id","trust-score","expires-at","status", | |
| "escalation-authority","allowed-actions","restricted-actions"] | |
| missing = filter (`notElem` covered) required | |
| in if null missing | |
| then (True, "V7:SEAL_COVERAGE_OK covers=" ++ show (length covered) ++ " fields") | |
| else (False, "V7:SEAL_COVERAGE_INCOMPLETE missing=" ++ show missing) | |
| -- V8: Blocked actions β globally forbidden actions must not be allowed | |
| checkBlockedActions :: DeedSpec -> TrustDeed -> (Bool, String) | |
| checkBlockedActions spec d = | |
| let blocked = map (map toLower) (specBlockedActions spec) | |
| present = filter (\a -> any (`isPrefixOf` map toLower a) blocked) (deedAllowed d) | |
| in if null present | |
| then (True, "V8:NO_BLOCKED_ACTIONS") | |
| else (False, "V8:BLOCKED_ACTION_IN_ALLOWED actions=" ++ show present) | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- Deed validation result | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| data DeedValidation = DeedValidation | |
| { dv_agent :: String | |
| , dv_v1 :: (Bool, String) | |
| , dv_v2 :: (Bool, String) | |
| , dv_v3 :: (Bool, String) | |
| , dv_v4 :: (Bool, String) | |
| , dv_v5 :: (Bool, String) | |
| , dv_v6 :: (Bool, String) | |
| , dv_v7 :: (Bool, String) | |
| , dv_v8 :: (Bool, String) | |
| , dv_valid :: Bool | |
| , dv_reason :: String | |
| } deriving (Show) | |
| -- Linear: DeedToken %1-> ensures one validation per token | |
| validateDeed :: DeedSpec -> Integer -> DeedToken %1-> DeedValidation | |
| validateDeed spec nowTs (DeedToken deed) = | |
| let v1 = checkStructure deed | |
| v2 = checkDisjoint deed | |
| v3 = checkTrustScore spec deed | |
| v4 = checkSealFormat spec deed | |
| v5 = checkAuthorityModel spec deed | |
| v6 = checkExpiration spec deed nowTs | |
| v7 = checkSealCoverage spec deed | |
| v8 = checkBlockedActions spec deed | |
| allChecks = [v1, v2, v3, v4, v5, v6, v7, v8] | |
| allOk = all fst allChecks | |
| firstFail = head $ filter (not . fst) allChecks ++ [(True, "ALL_CHECKS_PASSED")] | |
| in DeedValidation | |
| { dv_agent = deedAgent deed | |
| , dv_v1 = v1 | |
| , dv_v2 = v2 | |
| , dv_v3 = v3 | |
| , dv_v4 = v4 | |
| , dv_v5 = v5 | |
| , dv_v6 = v6 | |
| , dv_v7 = v7 | |
| , dv_v8 = v8 | |
| , dv_valid = allOk | |
| , dv_reason = snd firstFail | |
| } | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- Output | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| printDeedValidation :: DeedValidation -> IO () | |
| printDeedValidation dv = do | |
| putStrLn $ "agent=" ++ dv_agent dv | |
| putStrLn $ "valid=" ++ if dv_valid dv then "true" else "false" | |
| putStrLn $ "reason=" ++ dv_reason dv | |
| putStrLn $ "v1=" ++ checkLine (dv_v1 dv) | |
| putStrLn $ "v2=" ++ checkLine (dv_v2 dv) | |
| putStrLn $ "v3=" ++ checkLine (dv_v3 dv) | |
| putStrLn $ "v4=" ++ checkLine (dv_v4 dv) | |
| putStrLn $ "v5=" ++ checkLine (dv_v5 dv) | |
| putStrLn $ "v6=" ++ checkLine (dv_v6 dv) | |
| putStrLn $ "v7=" ++ checkLine (dv_v7 dv) | |
| putStrLn $ "v8=" ++ checkLine (dv_v8 dv) | |
| putStrLn "engine=haskell-deed-validator-lisp-v2" | |
| where | |
| checkLine (True, msg) = "pass:" ++ msg | |
| checkLine (False, msg) = "fail:" ++ msg | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- Main | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| main :: IO () | |
| main = do | |
| hSetBuffering stdout LineBuffering | |
| agentId <- getLine | |
| allowedStr <- getLine | |
| restrictedStr <- getLine | |
| scoreLine <- getLine | |
| sealStr <- getLine | |
| expiresLine <- getLine | |
| statusStr <- getLine | |
| escalationStr <- getLine | |
| specPathStr <- getLine | |
| let specPath = if specPathStr == "default" || null specPathStr | |
| then "bridges/lisp/deed-rules.lisp" | |
| else specPathStr | |
| spec <- loadDeedSpec specPath | |
| -- Unix "now" β approximate from expires_at sanity (subprocess has no clock access easily) | |
| -- Rust passes current timestamp as 10th line if needed; for now use 0 (expiry check is advisory) | |
| let nowTs = 0 :: Integer -- Rust's verify_integrity does authoritative expiry check | |
| let scoreD = read scoreLine :: Double | |
| expiresD = read expiresLine :: Integer | |
| allowed = splitOn ',' allowedStr | |
| restricted = splitOn ',' restrictedStr | |
| case (mkTrustScore spec scoreD, mkValidSeal spec sealStr) of | |
| (Left err, _) -> do | |
| putStrLn $ "agent=" ++ agentId | |
| putStrLn "valid=false" | |
| putStrLn $ "reason=" ++ err | |
| putStrLn "engine=haskell-deed-validator-lisp-v2" | |
| (_, Left err) -> do | |
| putStrLn $ "agent=" ++ agentId | |
| putStrLn "valid=false" | |
| putStrLn $ "reason=" ++ err | |
| putStrLn "engine=haskell-deed-validator-lisp-v2" | |
| (Right score, Right seal) -> do | |
| let deed = TrustDeed | |
| { deedAgent = agentId | |
| , deedAllowed = filter (not . null) allowed | |
| , deedRestricted = filter (not . null) restricted | |
| , deedScore = score | |
| , deedSeal = seal | |
| , deedExpiresAt = expiresD | |
| , deedStatus = map toLower statusStr | |
| , deedEscalation = escalationStr | |
| } | |
| token = mkDeedToken deed | |
| result = validateDeed spec nowTs token | |
| printDeedValidation result | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| -- Utilities | |
| -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| splitOn :: Char -> String -> [String] | |
| splitOn _ "" = [""] | |
| splitOn delim str = | |
| let (word, rest) = break (== delim) str | |
| in word : case rest of | |
| [] -> [] | |
| (_:xs) -> splitOn delim xs | |