diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,8 +1,11 @@
 # Changelog for biscuit-haskell
 
-## 0.4.0.1
+## 0.5.0.0
 
-- force datalog evaluation to WHNF when running the maxtime watchdog
+- biscuit v3.3 support
+- drop support for GHC <9.6
+- support for GHC 9.6, 9.8, 9.10, 9.12
+- dependencies update (including removal of cryptonite in favor of crypton)
 
 ## 0.4.0.0
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -6,8 +6,10 @@
 
 ## Supported biscuit versions
 
-The core library supports [`v2` biscuits][v2spec] (both open and sealed).
+The core library supports [`v3.0` to `v3.3` biscuits][spec] (both open and sealed).
 
+Only `ed25519` signatures are supported. `p256r1` signatures are not supported yet.
+
 ## How to use this library
 
 This library was designed with the use of [`QuasiQuotes`][quasiquotes] in mind.
@@ -103,7 +105,7 @@
 [gcouprie]: https://github.com/geal
 [biscuit]: https://www.clever-cloud.com/blog/engineering/2021/04/12/introduction-to-biscuit/
 [biscuittutorial]: https://www.clever-cloud.com/blog/engineering/2021/04/15/biscuit-tutorial/
-[v2spec]: https://github.com/CleverCloud/biscuit/blob/2.0/SPECIFICATIONS.md
+[spec]: https://github.com/eclipse-biscuit/biscuit/blob/main/SPECIFICATIONS.md
 [quasiquotes]: https://wiki.haskell.org/Quasiquotation
-[biscuitexample]: https://github.com/biscuit-auth/biscuit-haskell/blob/main/biscuit/src/Auth/Biscuit/Example.hs
-[packagedoc]: https://hackage.haskell.org/package/biscuit-haskell-0.1.0.0/docs/Auth-Biscuit.html
+[biscuitexample]: https://github.com/eclipse-biscuit/biscuit-haskell/blob/main/biscuit/src/Auth/Biscuit/Example.hs
+[packagedoc]: https://hackage.haskell.org/package/biscuit-haskell-0.4.0.0/docs/Auth-Biscuit.html
diff --git a/biscuit-haskell.cabal b/biscuit-haskell.cabal
--- a/biscuit-haskell.cabal
+++ b/biscuit-haskell.cabal
@@ -1,7 +1,7 @@
-cabal-version: 2.0
+cabal-version: 3.0
 
 name:           biscuit-haskell
-version:        0.4.0.1
+version:        0.5.0.0
 category:       Security
 synopsis:       Library support for the Biscuit security token
 description:    Please see the README on GitHub at <https://github.com/biscuit-auth/biscuit-haskell#readme>
@@ -10,10 +10,10 @@
 author:         Clément Delafargue
 maintainer:     clement@delafargue.name
 copyright:      2021 Clément Delafargue
-license:        BSD3
+license:        BSD-3-Clause
 license-file:   LICENSE
 build-type:     Simple
-tested-with:    GHC ==9.0.2 || ==9.2.4 || ==9.6.5 || ==9.8.2
+tested-with:    GHC ==9.8.4 || ==9.10.3 || ==9.12.4 || ==9.14.1
 extra-source-files:
     README.md
     ChangeLog.md
@@ -50,29 +50,30 @@
     base                 >= 4.7 && <5,
     async                ^>= 2.2,
     base16               >= 0.3 && <2,
-    bytestring           >= 0.10 && <0.12,
+    bytestring           >= 0.10 && <0.13,
     text                 >= 1.2 && <3,
-    containers           ^>= 0.6,
-    cryptonite           >= 0.27 && < 0.31,
+    containers           >= 0.6 && <0.9,
+    crypton              ^>= 1.0,
     memory               >= 0.15 && < 0.19,
-    template-haskell     >= 2.16 && < 2.22,
-    base64               ^>= 0.4,
+    template-haskell     >= 2.16 && < 2.25,
+    base64               ^>= 1.0,
     cereal               ^>= 0.5,
     mtl                  >= 2.2 && < 2.4,
     parser-combinators   >= 1.2 && < 1.4,
     protobuf             ^>= 0.2,
-    random               >= 1.0 && < 1.3,
+    random               >= 1.0 && < 1.4,
     regex-tdfa           ^>= 1.3,
     th-lift-instances    ^>= 0.1,
-    time                 ^>= 1.9,
+    time                 >= 1.9 && < 2,
     validation-selective >= 0.1 && < 0.3,
-    megaparsec           >= 9.2 && < 9.7
+    megaparsec           >= 9.2 && < 9.8
   default-language: Haskell2010
 
 test-suite biscuit-haskell-test
   type: exitcode-stdio-1.0
   main-is: Spec.hs
   other-modules:
+      Spec.AST
       Spec.NewCrypto
       Spec.Executor
       Spec.Parser
@@ -91,13 +92,12 @@
       async
     , aeson
     , base >=4.7 && <5
-    , base16 >=0.3 && <2
+    , base16 >=0.3 && <2.0
     , base64
     , biscuit-haskell
     , bytestring
     , cereal
     , containers
-    , cryptonite
     , lens
     , lens-aeson
     , megaparsec
diff --git a/src/Auth/Biscuit.hs b/src/Auth/Biscuit.hs
--- a/src/Auth/Biscuit.hs
+++ b/src/Auth/Biscuit.hs
@@ -3,7 +3,7 @@
 {-|
   Module      : Auth.Biscuit
   Copyright   : © Clément Delafargue, 2021
-  License     : MIT
+  License     : BSD-3-Clause
   Maintainer  : clement@delafargue.name
   Haskell implementation for the Biscuit token.
 -}
@@ -12,14 +12,14 @@
   -- * The biscuit auth token
   -- $biscuitOverview
 
-  -- * Creating keypairs
+  -- * Creating key pairs
   -- $keypairs
     newSecret
   , toPublic
   , SecretKey
   , PublicKey
 
-  -- ** Parsing and serializing keypairs
+  -- ** Parsing and serializing key pairs
   , serializeSecretKeyHex
   , serializePublicKeyHex
   , parseSecretKeyHex
@@ -83,6 +83,9 @@
   , authorizeBiscuitWithLimits
   , Limits (..)
   , defaultLimits
+  , setExternFuncs
+  , withExternFunc
+  , withExternFuncs
   , ParseError (..)
   , ExecutionError (..)
   , AuthorizedBiscuit (..)
@@ -106,6 +109,7 @@
 
 import           Control.Monad                       ((<=<))
 import           Control.Monad.Identity              (runIdentity)
+import qualified Data.Base64.Types                   as B64 (extractBase64)
 import           Data.Bifunctor                      (first)
 import           Data.ByteString                     (ByteString)
 import qualified Data.ByteString.Base64.URL          as B64
@@ -127,7 +131,10 @@
 import           Auth.Biscuit.Datalog.Executor       (ExecutionError (..),
                                                       Limits (..),
                                                       MatchedQuery (..),
-                                                      defaultLimits)
+                                                      defaultLimits,
+                                                      setExternFuncs,
+                                                      withExternFunc,
+                                                      withExternFuncs)
 import           Auth.Biscuit.Datalog.Parser         (authorizer, block, query)
 import           Auth.Biscuit.Datalog.ScopedExecutor (AuthorizationSuccess (..),
                                                       getBindings,
@@ -158,7 +165,7 @@
                                                       queryAuthorizerFacts,
                                                       queryRawBiscuitFacts,
                                                       seal, serializeBiscuit)
-import Auth.Biscuit.Utils                            (decodeHex, encodeHex')
+import           Auth.Biscuit.Utils                  (decodeHex, encodeHex')
 import qualified Data.Text                           as Text
 
 
@@ -178,12 +185,12 @@
 --
 -- > -- Biscuit Open Verified means the token has valid signatures
 -- > -- and is open to further restriction
--- > buildToken :: Keypair -> IO (Biscuit Open Verified)
--- > buildToken keypair =
+-- > buildToken :: SecretKey -> IO (Biscuit Open Verified)
+-- > buildToken secret =
 -- >   -- the logic language has its own syntax, which can be typed directly in haskell
 -- >   -- source code thanks to QuasiQuotes. The datalog snippets are parsed at compile
 -- >   -- time, so a datalog error results in a compilation error, not a runtime error
--- >   mkBiscuit keypair [block|
+-- >   mkBiscuit secret [block|
 -- >       // the two first lines describe facts:
 -- >       // the token holder is identified as `user_1234`
 -- >       user("user_1234");
@@ -256,8 +263,8 @@
 -- Biscuits rely on public key cryptography: biscuits are signed with a secret key only known
 -- to the party which emits it. Verifying a biscuit, on the other hand, can be done with a
 -- public key that can be widely distributed. A private key and its corresponding public key
--- is called a keypair, but since a public key can be deterministically computed from a
--- private key, owning a private key is the same as owning a keypair.
+-- is called a key pair, but since a public key can be deterministically computed from a
+-- private key, owning a private key is the same as owning a key pair.
 
 -- | Generate a new random 'SecretKey'
 newSecret :: IO SecretKey
@@ -351,28 +358,28 @@
 
 -- | Serialize a biscuit to URL-compatible base 64, as recommended by the spec
 serializeB64 :: BiscuitProof p => Biscuit p Verified -> ByteString
-serializeB64 = B64.encodeBase64' . serialize
+serializeB64 = B64.extractBase64. B64.encodeBase64' . serialize
 
 -- | Generate a base64-encoded third-party block request. It can be used in
 -- conjunction with 'mkThirdPartyBlockB64' to generate a base64-encoded
 -- third-party block, which can be then appended to a token with
 -- 'applyThirdPartyBlockB64'.
 mkThirdPartyBlockReqB64 :: Biscuit Open c -> ByteString
-mkThirdPartyBlockReqB64 = B64.encodeBase64' . mkThirdPartyBlockReq
+mkThirdPartyBlockReqB64 = B64.extractBase64 . B64.encodeBase64' . mkThirdPartyBlockReq
 
 -- | Given a base64-encoded third-party block request, generate a base64-encoded
 -- third-party block, which can be then appended to a token with
 -- 'applyThirdPartyBlockB64'.
 mkThirdPartyBlockB64 :: SecretKey -> ByteString -> Block -> Either String ByteString
 mkThirdPartyBlockB64 sk reqB64 b = do
-  req <- first unpack $ B64.decodeBase64 reqB64
+  req <- first unpack $ B64.decodeBase64Untyped reqB64
   contents <- mkThirdPartyBlock sk req b
-  pure $ B64.encodeBase64' contents
+  pure . B64.extractBase64 $ B64.encodeBase64' contents
 
 -- | Given a base64-encoded third-party block, append it to a token.
 applyThirdPartyBlockB64 :: Biscuit Open check -> ByteString -> Either String (IO (Biscuit Open check))
 applyThirdPartyBlockB64 b contentsB64 = do
-  contents <- first unpack $ B64.decodeBase64 contentsB64
+  contents <- first unpack $ B64.decodeBase64Untyped contentsB64
   applyThirdPartyBlock b contents
 
 -- $biscuitBlocks
@@ -398,11 +405,11 @@
 -- they carry are not visible outside themselves, only their checks are evaluated.
 --
 -- Third-party blocks lift this limitation by carrying an extra signature, crafted with a
--- dedicated keypair. This way, the token authorizer (as well as blocks themselves) can
--- opt-in to trust facts coming from third-party blocks signed with specific keypairs.
+-- dedicated key pair. This way, the token authorizer (as well as blocks themselves) can
+-- opt-in to trust facts coming from third-party blocks signed with specific key pairs.
 --
 -- For instance, adding `check if group("admin") trusting {publicKey};` to a token will
--- make it usable only if it carries a third party-block signed by the corresponding keypair,
+-- make it usable only if it carries a third party-block signed by the corresponding key pair,
 -- and carrying a `group("admin")` fact.
 --
 -- Since it is not desirable to share the token with the external entity providing the third-party
diff --git a/src/Auth/Biscuit/Crypto.hs b/src/Auth/Biscuit/Crypto.hs
--- a/src/Auth/Biscuit/Crypto.hs
+++ b/src/Auth/Biscuit/Crypto.hs
@@ -1,21 +1,31 @@
 {-# LANGUAGE CPP                        #-}
 {-# LANGUAGE DerivingStrategies         #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings          #-}
 {-# LANGUAGE QuasiQuotes                #-}
 {-# LANGUAGE TemplateHaskell            #-}
 {-# LANGUAGE TupleSections              #-}
 {-# LANGUAGE TypeApplications           #-}
+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
+{-|
+  Module      : Auth.Biscuit.Crypto
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+  Maintainer  : clement@delafargue.name
+  Cryptographic helpers for biscuit signatures
+-}
 module Auth.Biscuit.Crypto
   ( SignedBlock
   , Blocks
-  , signBlock
+  , signAuthority
+  , signAttenuationBlock
   , signExternalBlock
-  , sign3rdPartyBlock
+  , sign3rdPartyBlockV1
   , verifyBlocks
   , verifySecretProof
   , verifySignatureProof
   , getSignatureProof
-  , verifyExternalSig
+  , verifyExternalSigV1
   , PublicKey
   , pkBytes
   , readEd25519PublicKey
@@ -38,8 +48,7 @@
 import           Data.Function              (on)
 import           Data.Int                   (Int32)
 import           Data.List.NonEmpty         (NonEmpty (..))
-import qualified Data.List.NonEmpty         as NE
-import           Data.Maybe                 (catMaybes, fromJust)
+import           Data.Maybe                 (fromJust, fromMaybe, isJust)
 import           Instances.TH.Lift          ()
 import           Language.Haskell.TH.Syntax
 
@@ -103,9 +112,22 @@
 skBytes :: SecretKey -> ByteString
 skBytes (SecretKey sk) = convert sk
 
-type SignedBlock = (ByteString, Signature, PublicKey, Maybe (Signature, PublicKey))
+type SignedBlock =
+  ( ByteString -- payload
+  , Signature -- signature
+  , PublicKey -- nextKey
+  , Maybe (Signature, PublicKey) -- externalKey
+  , Maybe Int -- version
+  )
 type Blocks = NonEmpty SignedBlock
 
+type AnySignedBlock a =
+  ( ByteString -- payload
+  , a
+  , PublicKey -- nextKey
+  , Maybe (Signature, PublicKey) -- externalKey
+  , Maybe Int -- version
+  )
 -- | Biscuit 2.0 allows multiple signature algorithms.
 -- For now this lib only supports Ed25519, but the spec mandates flagging
 -- each publicKey with an algorithm identifier when serializing it. The
@@ -121,97 +143,195 @@
       algBytes = PB.runPut $ PB.putInt32le algId
    in algBytes <> keyBytes
 
-signBlock :: SecretKey
-          -> ByteString
-          -> Maybe (Signature, PublicKey)
-          -> IO (SignedBlock, SecretKey)
-signBlock sk payload eSig = do
+signBlockV0 :: SecretKey
+            -> ByteString
+            -> Maybe (Signature, PublicKey)
+            -> IO (SignedBlock, SecretKey)
+signBlockV0 sk payload eSig = do
   let pk = toPublic sk
   (nextPk, nextSk) <- (toPublic &&& id) <$> generateSecretKey
-  let toSign = getToSig (payload, (), nextPk, eSig)
+  let toSign = getSignaturePayloadV0 (payload, (), nextPk, eSig, Nothing)
       sig = sign sk pk toSign
-  pure ((payload, sig, nextPk, eSig), nextSk)
+  pure ((payload, sig, nextPk, eSig, Nothing), nextSk)
 
-signExternalBlock :: SecretKey
-                  -> SecretKey
-                  -> PublicKey
-                  -> ByteString
-                  -> IO (SignedBlock, SecretKey)
-signExternalBlock sk eSk pk payload =
-  let eSig = sign3rdPartyBlock eSk pk payload
-   in signBlock sk payload (Just eSig)
+signExternalBlockV0 :: SecretKey
+                    -> SecretKey
+                    -> PublicKey
+                    -> ByteString
+                    -> IO (SignedBlock, SecretKey)
+signExternalBlockV0 sk eSk pk payload =
+  let eSig = sign3rdPartyBlockV0 eSk pk payload
+   in signBlockV0 sk payload (Just eSig)
 
-sign3rdPartyBlock :: SecretKey
-                  -> PublicKey
-                  -> ByteString
-                  -> (Signature, PublicKey)
-sign3rdPartyBlock eSk nextPk payload =
+sign3rdPartyBlockV0 :: SecretKey
+                    -> PublicKey
+                    -> ByteString
+                    -> (Signature, PublicKey)
+sign3rdPartyBlockV0 eSk nextPk payload =
   let toSign = payload <> serializePublicKey nextPk
       ePk = toPublic eSk
       eSig = sign eSk ePk toSign
    in (eSig, ePk)
 
 getSignatureProof :: SignedBlock -> SecretKey -> Signature
-getSignatureProof (lastPayload, Signature lastSig, lastPk, _todo) nextSecret =
+getSignatureProof (lastPayload, Signature lastSig, lastPk, _, _) nextSecret =
   let sk = nextSecret
       pk = toPublic nextSecret
       toSign = lastPayload <> serializePublicKey lastPk <> lastSig
    in sign sk pk toSign
 
-getToSig :: (ByteString, a, PublicKey, Maybe (Signature, PublicKey)) -> ByteString
-getToSig (p, _, nextPk, ePk) =
+getSignaturePayloadV0 :: AnySignedBlock a -> ByteString
+getSignaturePayloadV0 (p, _, nextPk, ePk, _) =
   p <> foldMap (sigBytes . fst) ePk <> serializePublicKey nextPk
 
-getSignature :: SignedBlock -> Signature
-getSignature (_, sig, _, _) = sig
-
-getPublicKey :: SignedBlock -> PublicKey
-getPublicKey (_, _, pk, _) = pk
-
 -- | The data signed by the external key is the payload for the current block + the public key from
 -- the previous block: this prevents signature reuse (the external signature cannot be used on another
 -- token)
-getExternalSigPayload :: PublicKey -> SignedBlock -> Maybe (PublicKey, ByteString, Signature)
-getExternalSigPayload pkN (payload, _, _, Just (eSig, ePk)) = Just (ePk, payload <> serializePublicKey pkN, eSig)
-getExternalSigPayload _ _ = Nothing
+getExternalSignaturePayloadV0 :: PublicKey -> SignedBlock -> Maybe (PublicKey, ByteString, Signature)
+getExternalSignaturePayloadV0 pkN (payload, _, _, Just (eSig, ePk), _) = Just (ePk, payload <> serializePublicKey pkN, eSig)
+getExternalSignaturePayloadV0 _ _ = Nothing
 
+getAuthoritySignaturePayloadV1 :: ByteString -> PublicKey -> ByteString
+getAuthoritySignaturePayloadV1 p nextPk =
+  "\0BLOCK\0" <>
+  "\0VERSION\0" <> PB.runPut (PB.putInt32le 1) <>
+  "\0PAYLOAD\0" <> p <>
+  serializePublicKeyV1 nextPk
+
+getBlockSignaturePayloadV1 :: ByteString -> PublicKey -> Maybe (Signature, PublicKey) -> Signature -> ByteString
+getBlockSignaturePayloadV1 p nextPk ePk prevSig =
+  getAuthoritySignaturePayloadV1 p nextPk <>
+    "\0PREVSIG\0" <> sigBytes prevSig <>
+    foldMap serializeExternalSignatureV1 ePk
+
+getExternalSignaturePayloadV1 :: ByteString -> Signature -> ByteString
+getExternalSignaturePayloadV1 payload prevSig =
+  "\0EXTERNAL\0" <>
+  "\0VERSION\0" <> PB.runPut (PB.putInt32le 1) <>
+  "\0PAYLOAD\0" <> payload <>
+  "\0PREVSIG\0" <> sigBytes prevSig
+
+serializePublicKeyV1 :: PublicKey -> ByteString
+serializePublicKeyV1 pk =
+  let keyBytes = pkBytes pk
+      algId :: Int32
+      algId = fromIntegral $ fromEnum PB.Ed25519
+      -- The spec mandates that we serialize the algorithm id as a little-endian int32
+      algBytes = PB.runPut $ PB.putInt32le algId
+   in "\0ALGORITHM\0" <> algBytes <>
+      "\0NEXTKEY\0" <> keyBytes
+
+serializeExternalSignatureV1 :: (Signature, PublicKey) -> ByteString
+serializeExternalSignatureV1 (sig, _) = "\0EXTERNALSIG\0" <> sigBytes sig
+
+getSignature :: SignedBlock -> Signature
+getSignature (_, sig, _, _, _) = sig
+
+getPublicKey :: SignedBlock -> PublicKey
+getPublicKey (_, _, pk, _, _) = pk
+
 -- | When adding a pre-signed third-party block to a token, we make sure the third-party block is correctly
 -- signed (pk-signature match, and the third-party block is pinned to the last biscuit block)
-verifyExternalSig :: PublicKey -> (ByteString, Signature, PublicKey) -> Bool
-verifyExternalSig previousPk (payload, eSig, ePk) =
+verifyExternalSigV0 :: PublicKey -> (ByteString, Signature, PublicKey) -> Bool
+verifyExternalSigV0 previousPk (payload, eSig, ePk) =
   verify ePk (payload <> serializePublicKey previousPk) eSig
 
+-- | When adding a pre-signed third-party block to a token, we make sure the third-party block is correctly
+-- signed (pk-signature match, and the third-party block is pinned to the last biscuit block)
+verifyExternalSigV1 :: Signature -> (ByteString, Signature, PublicKey) -> Bool
+verifyExternalSigV1 prevSig (payload, eSig, ePk) =
+  verify ePk (getExternalSignaturePayloadV1 payload prevSig) eSig
+
+verifyAuthorityBlock :: SignedBlock -> PublicKey -> Bool
+verifyAuthorityBlock b@(payload, sig, nextPk, _, version) rootPk =
+  case fromMaybe 0 version of
+    0 -> verify rootPk (getSignaturePayloadV0 b) sig
+    1 -> verify rootPk (getAuthoritySignaturePayloadV1 payload nextPk) sig
+    _ -> False
+
+verifyAttenuationBlock :: SignedBlock -> SignedBlock -> Bool
+verifyAttenuationBlock block previousBlock =
+  let (payload, sig, nextPk, eSig', version) = block
+      (_, prevSig, pk, _, _) = previousBlock
+   in case (fromMaybe 0 version, eSig') of
+        (0, Nothing) -> verify pk (getSignaturePayloadV0 block) sig
+        (0, Just _)  -> False -- reject third-party blocks with v0 signatures
+        (1, Nothing) -> verify pk (getBlockSignaturePayloadV1 payload nextPk eSig' prevSig) sig
+        (1, Just (eSig, ePk)) ->
+          let sv = verify pk (getBlockSignaturePayloadV1 payload nextPk eSig' prevSig) sig
+              ev = verify ePk (getExternalSignaturePayloadV1 payload prevSig) eSig
+           in sv && ev
+        _          -> False
+
 verifyBlocks :: Blocks
              -> PublicKey
              -> Bool
-verifyBlocks blocks rootPk =
-  let attachKey pk (payload, sig) = (pk, payload, sig)
-      uncurry3 f (a, b, c) = f a b c
-      sigs = getSignature <$> blocks
-      toSigs = getToSig <$> blocks
-      -- key for block 0 is the root key
-      -- key for block n is the key from block (n - 1)
-      keys = pure rootPk <> (getPublicKey <$> blocks)
-      keysPayloadsSigs = NE.zipWith attachKey keys (NE.zip toSigs sigs)
-
-      -- external_signature(block_n) = sign(external_key_n, payload_n <> public_key_n-1)
-      -- so we need to pair each block with the public key carried by the previous block
-      -- (the authority block can't have an external signature)
-      previousKeys = getPublicKey <$> NE.init blocks
-      blocksAfterAuthority = NE.tail blocks
-      eKeysPayloadsESigs = catMaybes $ zipWith getExternalSigPayload previousKeys blocksAfterAuthority
-   in  all (uncurry3 verify) keysPayloadsSigs
-    && all (uncurry3 verify) eKeysPayloadsESigs
+verifyBlocks (authority :| attenuationBlocks) rootPk =
+  let attenuationBlocks' = zip attenuationBlocks (authority : attenuationBlocks)
+   in verifyAuthorityBlock authority rootPk
+  && all (uncurry verifyAttenuationBlock) attenuationBlocks'
 
 verifySecretProof :: SecretKey
                   -> SignedBlock
                   -> Bool
-verifySecretProof nextSecret (_, _, lastPk, _) =
+verifySecretProof nextSecret (_, _, lastPk, _, _) =
   lastPk == toPublic nextSecret
 
+
 verifySignatureProof :: Signature
                      -> SignedBlock
                      -> Bool
-verifySignatureProof extraSig (lastPayload, Signature lastSig, lastPk, _) =
+verifySignatureProof extraSig (lastPayload, Signature lastSig, lastPk, _, _) =
   let toSign = lastPayload <> serializePublicKey lastPk <> lastSig
    in verify lastPk toSign extraSig
+
+signAuthorityBlockV1 :: SecretKey -> ByteString -> IO (SignedBlock, SecretKey)
+signAuthorityBlockV1 sk payload = do
+  let pk = toPublic sk
+  (nextPk, nextSk) <- (toPublic &&& id) <$> generateSecretKey
+  let toSign = getAuthoritySignaturePayloadV1 payload nextPk
+      sig = sign sk pk toSign
+  pure ((payload, sig, nextPk, Nothing, Just 1), nextSk)
+
+signAttenuationBlockV1 :: SecretKey -> Signature -> ByteString -> Maybe (Signature, PublicKey) -> IO (SignedBlock, SecretKey)
+signAttenuationBlockV1 sk prevSig payload ePk = do
+  let pk = toPublic sk
+  (nextPk, nextSk) <- (toPublic &&& id) <$> generateSecretKey
+  let toSign = getBlockSignaturePayloadV1 payload nextPk ePk prevSig
+      sig = sign sk pk toSign
+  pure ((payload, sig, nextPk, ePk, Just 1), nextSk)
+
+sign3rdPartyBlockV1 :: SecretKey
+                    -> Signature
+                    -> ByteString
+                    -> (Signature, PublicKey)
+sign3rdPartyBlockV1 eSk prevSig payload =
+  let toSign = getExternalSignaturePayloadV1 payload prevSig
+      ePk = toPublic eSk
+      eSig = sign eSk ePk toSign
+   in (eSig, ePk)
+
+signAuthority :: SecretKey
+              -> (ByteString, Int)
+              -> IO (SignedBlock, SecretKey)
+signAuthority secretKey (payload, blockVersion)
+  | blockVersion >= 6 = signAuthorityBlockV1 secretKey payload
+  | otherwise = signBlockV0 secretKey payload Nothing
+
+signAttenuationBlock :: SecretKey
+                     -> Signature
+                     -> (ByteString, Int)
+                     -> Maybe (Signature, PublicKey)
+                     -> IO (SignedBlock, SecretKey)
+signAttenuationBlock secretKey prevSig (payload, blockVersion) ePk
+  | blockVersion >= 6 || isJust ePk = signAttenuationBlockV1 secretKey prevSig payload ePk
+  | otherwise = signBlockV0 secretKey payload ePk
+
+signExternalBlock :: SecretKey
+                  -> Signature
+                  -> (ByteString, Int)
+                  -> SecretKey
+                  -> IO (SignedBlock, SecretKey)
+signExternalBlock secretKey prevSig (payload, blockVersion) eSk =
+   let ePk = sign3rdPartyBlockV1 eSk prevSig payload
+    in signAttenuationBlock secretKey prevSig (payload, blockVersion) (Just ePk)
diff --git a/src/Auth/Biscuit/Datalog/AST.hs b/src/Auth/Biscuit/Datalog/AST.hs
--- a/src/Auth/Biscuit/Datalog/AST.hs
+++ b/src/Auth/Biscuit/Datalog/AST.hs
@@ -20,7 +20,7 @@
 {-|
   Module      : Auth.Biscuit.Datalog.AST
   Copyright   : © Clément Delafargue, 2021
-  License     : MIT
+  License     : BSD-3-Clause
   Maintainer  : clement@delafargue.name
   The Datalog elements
 -}
@@ -64,6 +64,9 @@
   , RuleScope
   , EvalRuleScope
   , SetType
+  , ArrayType
+  , MapType
+  , MapKey (..)
   , Slice (..)
   , PkOrSlice (..)
   , SliceType
@@ -89,12 +92,17 @@
   , queryHasNoV4Operators
   , ruleHasNoScope
   , ruleHasNoV4Operators
+  , ruleHasNoV6Values
+  , predicateHasNoV6Values
+  , checkHasNoV6Values
   , isCheckOne
+  , isReject
   , renderBlock
   , renderAuthorizer
   , renderFact
   , renderRule
   , valueToSetTerm
+  , setValueToValue
   , toStack
   , substituteAuthorizer
   , substituteBlock
@@ -187,6 +195,19 @@
   SetType 'NotWithinSet ctx = Set (Term' 'WithinSet 'InFact ctx)
   SetType 'WithinSet    ctx = Void
 
+type family ArrayType (inSet:: IsWithinSet) (ctx :: DatalogContext) where
+  ArrayType 'NotWithinSet ctx = [Term' 'NotWithinSet 'InFact ctx]
+  ArrayType 'WithinSet    ctx = Void
+
+data MapKey
+  = IntKey Int64
+  | StringKey Text
+  deriving (Eq, Show, Ord, Lift)
+
+type family MapType (inSet:: IsWithinSet) (ctx :: DatalogContext) where
+  MapType 'NotWithinSet ctx = Map MapKey (Term' 'NotWithinSet 'InFact ctx)
+  MapType 'WithinSet    ctx = Void
+
 type family BlockIdType (evalCtx :: EvaluationContext) (ctx :: DatalogContext) where
   BlockIdType 'Repr 'WithSlices     = PkOrSlice
   BlockIdType 'Repr 'Representation = PublicKey
@@ -211,21 +232,33 @@
   | Antiquote (SliceType ctx)
   -- ^ A slice (eg. @{name}@)
   | TermSet (SetType inSet ctx)
-  -- ^ A set (eg. @[true, false]@)
+  -- ^ A set (eg. @{true, false}@)
+  | LNull
+  -- ^ @null@
+  | TermArray (ArrayType inSet ctx)
+  -- ^ An array (eg. @[1, true, []]@)
+  | TermMap (MapType inSet ctx)
+  -- ^ A map (eg @{"key": true, 1: null}@)
 
 deriving instance ( Eq (VariableType inSet pof)
                   , Eq (SliceType ctx)
                   , Eq (SetType inSet ctx)
+                  , Eq (ArrayType inSet ctx)
+                  , Eq (MapType inSet ctx)
                   ) => Eq (Term' inSet pof ctx)
 
 deriving instance ( Ord (VariableType inSet pof)
                   , Ord (SliceType ctx)
                   , Ord (SetType inSet ctx)
+                  , Ord (ArrayType inSet ctx)
+                  , Ord (MapType inSet ctx)
                   ) => Ord (Term' inSet pof ctx)
 
 deriving instance ( Show (VariableType inSet pof)
                   , Show (SliceType ctx)
                   , Show (SetType inSet ctx)
+                  , Show (ArrayType inSet ctx)
+                  , Show (MapType inSet ctx)
                   ) => Show (Term' inSet pof ctx)
 
 -- | In a regular AST, slices have already been eliminated
@@ -239,17 +272,22 @@
 
 instance  ( Lift (VariableType inSet pof)
           , Lift (SetType inSet ctx)
+          , Lift (ArrayType inSet ctx)
+          , Lift (MapType inSet ctx)
           , Lift (SliceType ctx)
           )
          => Lift (Term' inSet pof ctx) where
-  lift (Variable n)    = [| Variable n |]
-  lift (LInteger i)    = [| LInteger i |]
-  lift (LString s)     = [| LString s |]
-  lift (LBytes bs)     = [| LBytes bs |]
-  lift (LBool b)       = [| LBool  b |]
-  lift (TermSet terms) = [| TermSet terms |]
-  lift (LDate t)       = [| LDate (read $(lift $ show t)) |]
-  lift (Antiquote s)   = [| s |]
+  lift (Variable n)      = [| Variable n |]
+  lift (LInteger i)      = [| LInteger i |]
+  lift (LString s)       = [| LString s |]
+  lift (LBytes bs)       = [| LBytes bs |]
+  lift (LBool b)         = [| LBool  b |]
+  lift (TermSet terms)   = [| TermSet terms |]
+  lift (LDate t)         = [| LDate (read $(lift $ show t)) |]
+  lift LNull             = [| LNull |]
+  lift (Antiquote s)     = [| s |]
+  lift (TermArray terms) = [| TermArray terms |]
+  lift (TermMap terms)   = [| TermMap terms |]
 
 #if MIN_VERSION_template_haskell(2,17,0)
   liftTyped = liftCode . unsafeTExpCoerce . lift
@@ -323,10 +361,27 @@
   LDate i     -> Just $ LDate i
   LBytes i    -> Just $ LBytes i
   LBool i     -> Just $ LBool i
+  LNull       -> Just LNull
   TermSet _   -> Nothing
+  TermArray _ -> Nothing
+  TermMap _ -> Nothing
   Variable v  -> absurd v
   Antiquote v -> absurd v
 
+setValueToValue :: SetValue -> Value
+setValueToValue = \case
+  LInteger i  -> LInteger i
+  LString i   -> LString i
+  LDate i     -> LDate i
+  LBytes i    -> LBytes i
+  LBool i     -> LBool i
+  LNull       -> LNull
+  TermSet v   -> absurd v
+  TermArray v -> absurd v
+  TermMap v   -> absurd v
+  Variable v  -> absurd v
+  Antiquote v -> absurd v
+
 valueToTerm :: Value -> Term
 valueToTerm = \case
   LInteger i  -> LInteger i
@@ -334,15 +389,20 @@
   LDate i     -> LDate i
   LBytes i    -> LBytes i
   LBool i     -> LBool i
+  LNull       -> LNull
   TermSet i   -> TermSet i
+  TermArray i -> TermArray i
+  TermMap i   -> TermMap i
   Variable v  -> absurd v
   Antiquote v -> absurd v
 
 renderId' :: (VariableType inSet pof -> Text)
           -> (SetType inSet ctx -> Text)
+          -> (ArrayType inSet ctx -> Text)
+          -> (MapType inSet ctx -> Text)
           -> (SliceType ctx -> Text)
           -> Term' inSet pof ctx -> Text
-renderId' var set slice = \case
+renderId' var rset rarray rmap rslice = \case
   Variable name -> var name
   LInteger int  -> pack $ show int
   LString str   -> pack $ show str
@@ -350,20 +410,64 @@
   LBytes bs     -> "hex:" <> encodeHex bs
   LBool True    -> "true"
   LBool False   -> "false"
-  TermSet terms -> set terms
-  Antiquote v   -> slice v
+  LNull         -> "null"
+  TermSet terms -> rset terms
+  TermArray terms -> rarray terms
+  TermMap terms -> rmap terms
+  Antiquote v   -> rslice v
 
 renderSet :: (SliceType ctx -> Text)
           -> Set (Term' 'WithinSet 'InFact ctx)
           -> Text
-renderSet slice terms =
-  "[" <> intercalate "," (renderId' absurd absurd slice <$> Set.toList terms) <> "]"
+renderSet rslice terms =
+  if null terms
+  then "{,}"
+  else
+    "{" <> intercalate "," (renderId' absurd absurd absurd absurd rslice <$> Set.toList terms) <> "}"
 
+renderArray :: (SliceType ctx -> Text)
+            -> [Term' 'NotWithinSet 'InFact ctx]
+            -> Text
+renderArray rslice terms =
+  let renderElem = renderId'
+        absurd
+        (renderSet rslice)
+        (renderArray rslice)
+        (renderMap rslice)
+        rslice
+   in "[" <> intercalate "," (renderElem <$> terms) <> "]"
+
+renderMap :: (SliceType ctx -> Text)
+          -> Map MapKey (Term' 'NotWithinSet 'InFact ctx)
+          -> Text
+renderMap rslice terms =
+  let renderElem = renderId'
+        absurd
+        (renderSet rslice)
+        (renderArray rslice)
+        (renderMap rslice)
+        rslice
+      renderKey (IntKey k)      = pack $ show k
+      renderKey (StringKey str) = pack $ show str
+      renderEntry (k,v) = renderKey k <> ": " <> renderElem v
+   in "{" <> intercalate "," (renderEntry <$> Map.toList terms) <> "}"
+
 renderId :: Term -> Text
-renderId = renderId' ("$" <>) (renderSet absurd) absurd
+renderId = renderId'
+  ("$" <>)
+  (renderSet absurd)
+  (renderArray absurd)
+  (renderMap absurd)
+  absurd
 
 renderFactId :: Term' 'NotWithinSet 'InFact 'Representation -> Text
-renderFactId = renderId' absurd (renderSet absurd) absurd
+renderFactId =
+  renderId'
+    absurd
+    (renderSet absurd)
+    (renderArray absurd)
+    (renderMap absurd)
+    absurd
 
 listSymbolsInTerm :: Term -> Set.Set Text
 listSymbolsInTerm = \case
@@ -453,7 +557,7 @@
         Just vs -> Failure vs
 
 
-data CheckKind = One | All
+data CheckKind = CheckOne | CheckAll | Reject
   deriving (Eq, Show, Ord, Lift)
 
 data Check' evalCtx ctx = Check
@@ -473,8 +577,11 @@
 type EvalCheck = Check' 'Eval 'Representation
 
 isCheckOne :: Check' evalCtx ctx -> Bool
-isCheckOne Check{cKind} = cKind == One
+isCheckOne Check{cKind} = cKind == CheckOne
 
+isReject :: Check' evalCtx ctx -> Bool
+isReject Check{cKind} = cKind == Reject
+
 data PolicyType = Allow | Deny
   deriving (Eq, Show, Ord, Lift)
 type Policy' evalCtx ctx = (PolicyType, Query' evalCtx ctx)
@@ -516,10 +623,11 @@
 
 renderCheck :: Check -> Text
 renderCheck Check{..} =
-  let kindToken = case cKind of
-        One -> "if"
-        All -> "all"
-   in "check " <> kindToken <> " " <>
+  let keyword = case cKind of
+        CheckOne -> "check if"
+        CheckAll -> "check all"
+        Reject   -> "reject if"
+   in keyword <> " " <>
       intercalate "\n or " (renderQueryItem <$> cQueries)
 
 listSymbolsInQueryItem :: QueryItem' evalCtx 'Representation -> Set.Set Text
@@ -604,6 +712,40 @@
 ruleHasNoV4Operators Rule{expressions} =
   all expressionHasNoV4Operators expressions
 
+expressionHasNoV6ValuesOrOperators :: Expression -> Bool
+expressionHasNoV6ValuesOrOperators = \case
+  EClosure _ _ -> False
+  EBinary HeterogeneousEqual _ _ -> False
+  EBinary HeterogeneousNotEqual _ _ -> False
+  EBinary LazyAnd _ _ -> False
+  EBinary LazyOr _ _ -> False
+  EBinary All _ _ -> False
+  EBinary Any _ _ -> False
+  EBinary _ l r -> expressionHasNoV6ValuesOrOperators l && expressionHasNoV6ValuesOrOperators r
+  EUnary _ l -> expressionHasNoV6ValuesOrOperators l
+  EValue LNull -> False
+  EValue _ -> True
+
+ruleHasNoV6Values :: Rule -> Bool
+ruleHasNoV6Values Rule{rhead, body, expressions} =
+         predicateHasNoV6Values rhead
+  && all predicateHasNoV6Values body
+  && all expressionHasNoV6ValuesOrOperators expressions
+
+predicateHasNoV6Values :: Predicate' a b -> Bool
+predicateHasNoV6Values Predicate{terms} =
+  let hasV6 = \case
+        LNull -> True
+        _ -> False
+   in not (any hasV6 terms)
+
+checkHasNoV6Values :: Check -> Bool
+checkHasNoV6Values Check{cQueries} =
+  let hasNoV6 QueryItem{qBody, qExpressions} =
+           all predicateHasNoV6Values qBody
+        && all expressionHasNoV6ValuesOrOperators qExpressions
+   in all hasNoV6 cQueries
+
 renderRule :: Rule -> Text
 renderRule Rule{rhead,body,expressions,scope} =
      renderPredicate rhead <> " <- "
@@ -634,9 +776,10 @@
         Variable name -> Set.singleton name
         _             -> Set.empty
    in \case
-        EValue t       -> keepVariable t
-        EUnary _ e     -> extractExprVariables e
-        EBinary _ e e' -> ((<>) `on` extractExprVariables) e e'
+        EValue t          -> keepVariable t
+        EUnary _ e        -> extractExprVariables e
+        EBinary _ e e'    -> ((<>) `on` extractExprVariables) e e'
+        EClosure params e -> extractExprVariables e Set.\\ Set.fromList params
 
 makeRule :: Predicate' 'InPredicate ctx
          -> [Predicate' 'InPredicate ctx]
@@ -656,6 +799,8 @@
     Negate
   | Parens
   | Length
+  | TypeOf
+  | UnaryFfi Text
   deriving (Eq, Ord, Show, Lift)
 
 data Binary =
@@ -680,12 +825,22 @@
   | BitwiseOr
   | BitwiseXor
   | NotEqual
+  | HeterogeneousEqual
+  | HeterogeneousNotEqual
+  | LazyAnd
+  | LazyOr
+  | All
+  | Any
+  | Get
+  | BinaryFfi Text
+  | Try
   deriving (Eq, Ord, Show, Lift)
 
 data Expression' (ctx :: DatalogContext) =
     EValue (Term' 'NotWithinSet 'InPredicate ctx)
   | EUnary Unary (Expression' ctx)
   | EBinary Binary (Expression' ctx) (Expression' ctx)
+  | EClosure [Text] (Expression' ctx)
 
 deriving instance Eq   (Term' 'NotWithinSet 'InPredicate ctx) => Eq (Expression' ctx)
 deriving instance Ord  (Term' 'NotWithinSet 'InPredicate ctx) => Ord (Expression' ctx)
@@ -699,21 +854,28 @@
   EValue t       -> listSymbolsInTerm t
   EUnary _ e     -> listSymbolsInExpression e
   EBinary _ e e' -> foldMap listSymbolsInExpression [e, e']
+  EClosure ps e  -> Set.fromList ps <> listSymbolsInExpression e
 
 data Op =
     VOp Term
   | UOp Unary
   | BOp Binary
+  | COp [Text] [Op]
+  deriving (Eq, Show)
 
 fromStack :: [Op] -> Either String Expression
 fromStack =
-  let go stack []                    = Right stack
+  let go :: [Expression] -> [Op] -> Either String [Expression]
+      go stack []                    = Right stack
       go stack        (VOp t : rest) = go (EValue t : stack) rest
       go (e:stack)    (UOp o : rest) = go (EUnary o e : stack) rest
       go []           (UOp _ : _)    = Left "Empty stack on unary op"
       go (e:e':stack) (BOp o : rest) = go (EBinary o e' e : stack) rest
       go [_]          (BOp _ : _)    = Left "Unary stack on binary op"
       go []           (BOp _ : _)    = Left "Empty stack on binary op"
+      go stack        (COp ps ops : rest) = do
+        e <- fromStack ops
+        go (EClosure ps e : stack) rest
       final []  = Left "Empty stack"
       final [x] = Right x
       final _   = Left "Stack containing more than one element"
@@ -722,9 +884,10 @@
 toStack :: Expression -> [Op]
 toStack expr =
   let go e s = case e of
-        EValue t      -> VOp t : s
-        EUnary o i    -> go i $ UOp o : s
-        EBinary o l r -> go l $ go r $ BOp o : s
+        EValue t       -> VOp t : s
+        EUnary o i     -> go i $ UOp o : s
+        EBinary o l r  -> go l $ go r $ BOp o : s
+        EClosure ps ce -> COp ps (toStack ce) : s
    in go expr []
 
 renderExpression :: Expression -> Text
@@ -736,16 +899,21 @@
                <> "." <> m <> "("
                <> renderExpression e'
                <> ")"
+      rC []  e = renderExpression e
+      rC [p] e = p <> " -> " <> renderExpression e
+      rC ps  e = "(" <> intercalate ", " ps <> ")" <> renderExpression e
    in \case
         EValue t                    -> renderId t
         EUnary Negate e             -> "!" <> renderExpression e
         EUnary Parens e             -> "(" <> renderExpression e <> ")"
         EUnary Length e             -> renderExpression e <> ".length()"
+        EUnary TypeOf e             -> renderExpression e <> ".type()"
+        EUnary (UnaryFfi n) e       -> renderExpression e <> ".extern::" <> n <> "()"
         EBinary LessThan e e'       -> rOp "<" e e'
         EBinary GreaterThan e e'    -> rOp ">" e e'
         EBinary LessOrEqual e e'    -> rOp "<=" e e'
         EBinary GreaterOrEqual e e' -> rOp ">=" e e'
-        EBinary Equal e e'          -> rOp "==" e e'
+        EBinary Equal e e'          -> rOp "===" e e'
         EBinary Contains e e'       -> rm "contains" e e'
         EBinary Prefix e e'         -> rm "starts_with" e e'
         EBinary Suffix e e'         -> rm "ends_with" e e'
@@ -761,7 +929,17 @@
         EBinary BitwiseAnd e e'     -> rOp "&" e e'
         EBinary BitwiseOr e e'      -> rOp "|" e e'
         EBinary BitwiseXor e e'     -> rOp "^" e e'
-        EBinary NotEqual e e'       -> rOp "!=" e e'
+        EBinary NotEqual e e'       -> rOp "!==" e e'
+        EBinary HeterogeneousEqual e e' -> rOp "==" e e'
+        EBinary HeterogeneousNotEqual e e' -> rOp "!=" e e'
+        EBinary LazyAnd e e'        -> rOp "&&" e e'
+        EBinary LazyOr e e'         -> rOp "||" e e'
+        EBinary All e e'            -> rm "all" e e'
+        EBinary Any e e'            -> rm "any" e e'
+        EBinary Get e e'            -> rm "get" e e'
+        EBinary (BinaryFfi n) e e'  -> rm ("extern::" <> n) e e'
+        EBinary Try e e'            -> rm "try_or" e e'
+        EClosure ps e               -> rC ps e
 
 -- | A biscuit block, containing facts, rules and checks.
 --
@@ -1081,8 +1259,13 @@
   LDate i     -> pure $ LDate i
   LBytes i    -> pure $ LBytes i
   LBool i     -> pure $ LBool i
+  LNull       -> pure LNull
   TermSet i   ->
     TermSet . Set.fromList <$> traverse (substituteSetTerm termMapping) (Set.toList i)
+  TermArray i ->
+    TermArray <$> traverse (substituteTerm termMapping) i
+  TermMap i ->
+    TermMap <$> traverse (substituteTerm termMapping) i
   Variable i  -> pure $ Variable i
   Antiquote (Slice v) -> maybe (failure v) (pure . valueToTerm) $ termMapping Map.!? v
 
@@ -1095,8 +1278,13 @@
   LDate i     -> pure $ LDate i
   LBytes i    -> pure $ LBytes i
   LBool i     -> pure $ LBool i
+  LNull       -> pure LNull
   TermSet i   ->
     TermSet . Set.fromList <$> traverse (substituteSetTerm termMapping) (Set.toList i)
+  TermArray i ->
+    TermArray <$> traverse (substituteTerm termMapping) i
+  TermMap i ->
+    TermMap <$> traverse (substituteTerm termMapping) i
   Variable v  -> absurd v
   Antiquote (Slice v) -> maybe (failure v) pure $ termMapping Map.!? v
 
@@ -1109,7 +1297,10 @@
   LDate i     -> pure $ LDate i
   LBytes i    -> pure $ LBytes i
   LBool i     -> pure $ LBool i
+  LNull       -> pure LNull
   TermSet v   -> absurd v
+  TermArray v -> absurd v
+  TermMap v   -> absurd v
   Variable v  -> absurd v
   Antiquote (Slice v) ->
     let setTerm = valueToSetTerm =<< termMapping Map.!? v
@@ -1123,6 +1314,7 @@
   EUnary op e -> EUnary op <$> substituteExpression termMapping e
   EBinary op e e' -> EBinary op <$> substituteExpression termMapping e
                                 <*> substituteExpression termMapping e'
+  EClosure ps e -> EClosure ps <$> substituteExpression termMapping e
 
 substituteScope :: Map Text PublicKey
                 -> RuleScope' 'Repr 'WithSlices
diff --git a/src/Auth/Biscuit/Datalog/Executor.hs b/src/Auth/Biscuit/Datalog/Executor.hs
--- a/src/Auth/Biscuit/Datalog/Executor.hs
+++ b/src/Auth/Biscuit/Datalog/Executor.hs
@@ -10,7 +10,7 @@
 {-|
   Module      : Auth.Biscuit.Datalog.Executor
   Copyright   : © Clément Delafargue, 2021
-  License     : MIT
+  License     : BSD-3-Clause
   Maintainer  : clement@delafargue.name
   The Datalog engine, tasked with deriving new facts from existing facts and rules, as well as matching available facts against checks and policies
 -}
@@ -20,6 +20,8 @@
   , ResultError (..)
   , Bindings
   , Name
+  , ExternFuncs
+  , ExternFunc (..)
   , MatchedQuery (..)
   , Scoped
   , FactGroup (..)
@@ -28,6 +30,9 @@
   , fromScopedFacts
   , keepAuthorized'
   , defaultLimits
+  , setExternFuncs
+  , withExternFunc
+  , withExternFuncs
   , evaluateExpression
   --
   , getFactsForRule
@@ -44,11 +49,12 @@
 import           Data.Foldable            (fold)
 import           Data.Functor.Compose     (Compose (..))
 import           Data.Int                 (Int64)
+import qualified Data.List                as List
 import           Data.List.NonEmpty       (NonEmpty)
 import qualified Data.List.NonEmpty       as NE
 import           Data.Map.Strict          (Map, (!?))
 import qualified Data.Map.Strict          as Map
-import           Data.Maybe               (isJust, mapMaybe)
+import           Data.Maybe               (fromMaybe, isJust, mapMaybe)
 import           Data.Set                 (Set)
 import qualified Data.Set                 as Set
 import           Data.Text                (Text, isInfixOf, unpack)
@@ -69,6 +75,21 @@
 -- | A list of bound variables, with the associated value
 type Bindings  = Map Name Value
 
+newtype ExternFunc = ExternFunc (Value -> Maybe Value -> Either String Value)
+
+instance Show ExternFunc where
+  show _ = "<extern func>"
+
+instance Eq ExternFunc where
+  _ == _ = True
+
+type ExternFuncs = Map Text ExternFunc
+
+runExternFunc :: ExternFuncs -> Text -> Value -> Maybe Value -> Either String Value
+runExternFunc ef name a1 a2 = do
+  ExternFunc func <- maybeToRight ("undefined external func " <> unpack name) $ ef !? name
+  func a1 a2
+
 -- | A datalog query that was matched, along with the values
 -- that matched
 data MatchedQuery
@@ -123,6 +144,7 @@
   , allowRegexes  :: Bool
   -- ^ whether or not allowing `.matches()` during verification (untrusted regex computation
   -- can enable DoS attacks). This security risk is mitigated by the 'maxTime' setting.
+  , externFuncs   :: ExternFuncs
   }
   deriving (Eq, Show)
 
@@ -131,15 +153,24 @@
 --   - 100 iterations
 --   - 1000μs max
 --   - regexes are allowed
---   - facts and rules are allowed in blocks
 defaultLimits :: Limits
 defaultLimits = Limits
   { maxFacts = 1000
   , maxIterations = 100
   , maxTime = 1000
   , allowRegexes = True
+  , externFuncs = mempty
   }
 
+withExternFunc :: Text -> (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+withExternFunc n f l@Limits{externFuncs} = l { externFuncs = Map.insert n (ExternFunc f) externFuncs }
+
+withExternFuncs :: Map Text (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+withExternFuncs fs l@Limits{externFuncs} = l { externFuncs = Map.union (ExternFunc <$> fs) externFuncs }
+
+setExternFuncs :: Map Text (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+setExternFuncs fs l = l { externFuncs = ExternFunc <$> fs }
+
 type Scoped a = (Set Natural, a)
 
 newtype FactGroup = FactGroup { getFactGroup :: Map (Set Natural) (Set Fact) }
@@ -191,14 +222,26 @@
 
 checkCheck :: Limits -> Natural -> Natural -> FactGroup -> EvalCheck -> Either String (Validation (NonEmpty Check) ())
 checkCheck l blockCount checkBlockId facts c@Check{cQueries,cKind} = do
-  let isQueryItemOk = case cKind of
-        One -> isQueryItemSatisfied l blockCount checkBlockId facts
-        All -> isQueryItemSatisfiedForAllMatches l blockCount checkBlockId facts
-  hasOkQueryItem <- anyM (fmap isJust . isQueryItemOk) cQueries
-  pure $ if hasOkQueryItem
-         then Success ()
-         else failure (toRepresentation c)
+  let queryMatchesOne = isQueryItemSatisfied l blockCount checkBlockId facts
+  let queryMatchesAll = isQueryItemSatisfiedForAllMatches l blockCount checkBlockId facts
 
+  case cKind of
+    CheckOne -> do
+       hasOkQueryItem <- anyM (fmap isJust . queryMatchesOne) cQueries
+       pure $ if hasOkQueryItem
+              then Success ()
+              else failure (toRepresentation c)
+    CheckAll -> do
+       hasOkQueryItem <- anyM (fmap isJust . queryMatchesAll) cQueries
+       pure $ if hasOkQueryItem
+              then Success ()
+              else failure (toRepresentation c)
+    Reject -> do
+       hasOkQueryItem <- anyM (fmap isJust . queryMatchesOne) cQueries
+       pure $ if not hasOkQueryItem
+              then Success ()
+              else failure (toRepresentation c)
+
 checkPolicy :: Limits -> Natural -> FactGroup -> EvalPolicy -> Either String (Maybe (Either MatchedQuery MatchedQuery))
 checkPolicy l blockCount facts (pType, query) = do
   bindings <- fold . fold <$> traverse (isQueryItemSatisfied l blockCount blockCount facts) query
@@ -274,7 +317,10 @@
       replaceTerm (LDate t)     = Just $ LDate t
       replaceTerm (LBytes t)    = Just $ LBytes t
       replaceTerm (LBool t)     = Just $ LBool t
+      replaceTerm LNull         = Just LNull
       replaceTerm (TermSet t)   = Just $ TermSet t
+      replaceTerm (TermArray t) = Just $ TermArray t
+      replaceTerm (TermMap t)   = Just $ TermMap t
       replaceTerm (Antiquote t) = absurd t
    in (\nt -> (origins, p { terms = nt})) <$> newTerms
 
@@ -329,6 +375,7 @@
 isSame (LBytes t)   (LBytes t')   = t == t'
 isSame (LBool t)    (LBool t')    = t == t'
 isSame (TermSet t)  (TermSet t')  = t == t'
+isSame LNull        LNull         = True
 isSame _ _                        = False
 
 -- | Given a predicate and a fact, try to match the fact to the predicate,
@@ -366,34 +413,57 @@
   LDate t     -> Right $ LDate t
   LBytes t    -> Right $ LBytes t
   LBool t     -> Right $ LBool t
+  LNull       -> Right LNull
   TermSet t   -> Right $ TermSet t
+  TermArray t   -> Right $ TermArray t
+  TermMap t   -> Right $ TermMap t
   Antiquote v -> absurd v
 
-evalUnary :: Unary -> Value -> Either String Value
-evalUnary Parens t = pure t
-evalUnary Negate (LBool b) = pure (LBool $ not b)
-evalUnary Negate _ = Left "Only booleans support negation"
-evalUnary Length (LString t) = pure . LInteger . fromIntegral $ ByteString.length $ Text.encodeUtf8 t
-evalUnary Length (LBytes bs) = pure . LInteger . fromIntegral $ ByteString.length bs
-evalUnary Length (TermSet s) = pure . LInteger . fromIntegral $ Set.size s
-evalUnary Length _ = Left "Only strings, bytes and sets support `.length()`"
+evalUnary :: Limits -> Unary -> Value -> Either String Value
+evalUnary _ Parens t = pure t
+evalUnary _ Negate (LBool b) = pure (LBool $ not b)
+evalUnary _ Negate _ = Left "Only booleans support negation"
+evalUnary _ Length (LString t) = pure . LInteger . fromIntegral $ ByteString.length $ Text.encodeUtf8 t
+evalUnary _ Length (LBytes bs) = pure . LInteger . fromIntegral $ ByteString.length bs
+evalUnary _ Length (TermSet s) = pure . LInteger . fromIntegral $ Set.size s
+evalUnary _ Length (TermArray s) = pure . LInteger . fromIntegral $ length s
+evalUnary _ Length (TermMap s) = pure . LInteger . fromIntegral $ Map.size s
+evalUnary _ Length _ = Left "Only strings, bytes, sets, arrays and maps support `.length()`"
+evalUnary _ TypeOf (LInteger _) = pure . LString $ "integer"
+evalUnary _ TypeOf (LString _) = pure . LString $ "string"
+evalUnary _ TypeOf (LDate _) = pure . LString $ "date"
+evalUnary _ TypeOf (LBytes _) = pure . LString $ "bytes"
+evalUnary _ TypeOf (LBool _) = pure . LString $ "bool"
+evalUnary _ TypeOf (TermSet _) = pure . LString $ "set"
+evalUnary _ TypeOf (TermArray _) = pure . LString $ "array"
+evalUnary _ TypeOf (TermMap _) = pure . LString $ "map"
+evalUnary _ TypeOf LNull = pure . LString $ "null"
+evalUnary _ TypeOf (Variable v) = absurd v
+evalUnary _ TypeOf (Antiquote v) = absurd v
+evalUnary Limits{externFuncs} (UnaryFfi n) v = runExternFunc externFuncs n v Nothing
 
 evalBinary :: Limits -> Binary -> Value -> Value -> Either String Value
 -- eq / ord operations
-evalBinary _ Equal (LInteger i) (LInteger i') = pure $ LBool (i == i')
-evalBinary _ Equal (LString t) (LString t')   = pure $ LBool (t == t')
-evalBinary _ Equal (LDate t) (LDate t')       = pure $ LBool (t == t')
-evalBinary _ Equal (LBytes t) (LBytes t')     = pure $ LBool (t == t')
-evalBinary _ Equal (LBool t) (LBool t')       = pure $ LBool (t == t')
-evalBinary _ Equal (TermSet t) (TermSet t')   = pure $ LBool (t == t')
-evalBinary _ Equal _ _                        = Left "Equality mismatch"
-evalBinary _ NotEqual (LInteger i) (LInteger i') = pure $ LBool (i /= i')
-evalBinary _ NotEqual (LString t) (LString t')   = pure $ LBool (t /= t')
-evalBinary _ NotEqual (LDate t) (LDate t')       = pure $ LBool (t /= t')
-evalBinary _ NotEqual (LBytes t) (LBytes t')     = pure $ LBool (t /= t')
-evalBinary _ NotEqual (LBool t) (LBool t')       = pure $ LBool (t /= t')
-evalBinary _ NotEqual (TermSet t) (TermSet t')   = pure $ LBool (t /= t')
-evalBinary _ NotEqual _ _                        = Left "Inequity mismatch"
+evalBinary _ Equal (LInteger i) (LInteger i')   = pure $ LBool (i == i')
+evalBinary _ Equal (LString t) (LString t')     = pure $ LBool (t == t')
+evalBinary _ Equal (LDate t) (LDate t')         = pure $ LBool (t == t')
+evalBinary _ Equal (LBytes t) (LBytes t')       = pure $ LBool (t == t')
+evalBinary _ Equal (LBool t) (LBool t')         = pure $ LBool (t == t')
+evalBinary _ Equal (TermSet t) (TermSet t')     = pure $ LBool (t == t')
+evalBinary _ Equal (TermArray t) (TermArray t') = pure $ LBool (t == t')
+evalBinary _ Equal (TermMap t) (TermMap t')     = pure $ LBool (t == t')
+evalBinary _ Equal _ _                          = Left "Equality mismatch"
+evalBinary _ NotEqual (LInteger i) (LInteger i')   = pure $ LBool (i /= i')
+evalBinary _ NotEqual (LString t) (LString t')     = pure $ LBool (t /= t')
+evalBinary _ NotEqual (LDate t) (LDate t')         = pure $ LBool (t /= t')
+evalBinary _ NotEqual (LBytes t) (LBytes t')       = pure $ LBool (t /= t')
+evalBinary _ NotEqual (LBool t) (LBool t')         = pure $ LBool (t /= t')
+evalBinary _ NotEqual (TermSet t) (TermSet t')     = pure $ LBool (t /= t')
+evalBinary _ NotEqual (TermArray t) (TermArray t') = pure $ LBool (t /= t')
+evalBinary _ NotEqual (TermMap t) (TermMap t')     = pure $ LBool (t /= t')
+evalBinary _ NotEqual _ _                          = Left "Inequity mismatch"
+evalBinary _ HeterogeneousEqual t t'             = pure $ LBool (t == t')
+evalBinary _ HeterogeneousNotEqual t t'          = pure $ LBool (t /= t')
 evalBinary _ LessThan (LInteger i) (LInteger i') = pure $ LBool (i < i')
 evalBinary _ LessThan (LDate t) (LDate t')       = pure $ LBool (t < t')
 evalBinary _ LessThan _ _                        = Left "< mismatch"
@@ -408,8 +478,10 @@
 evalBinary _ GreaterOrEqual _ _                        = Left ">= mismatch"
 -- string-related operations
 evalBinary _ Prefix (LString t) (LString t') = pure $ LBool (t' `Text.isPrefixOf` t)
-evalBinary _ Prefix _ _                      = Left "Only strings support `.starts_with()`"
+evalBinary _ Prefix (TermArray t) (TermArray t') = pure . LBool $ t' `List.isPrefixOf` t
+evalBinary _ Prefix _ _                      = Left "Only strings and arrays support `.starts_with()`"
 evalBinary _ Suffix (LString t) (LString t') = pure $ LBool (t' `Text.isSuffixOf` t)
+evalBinary _ Suffix (TermArray t) (TermArray t') = pure . LBool $ t' `List.isSuffixOf` t
 evalBinary _ Suffix _ _                      = Left "Only strings support `.ends_with()`"
 evalBinary Limits{allowRegexes} Regex  (LString t) (LString r) | allowRegexes = regexMatch t r
                                                                | otherwise    = Left "Regex evaluation is disabled"
@@ -437,17 +509,34 @@
 evalBinary _ And _ _ = Left "Only booleans support &&"
 evalBinary _ Or (LBool b) (LBool b') = pure $ LBool (b || b')
 evalBinary _ Or _ _ = Left "Only booleans support ||"
+evalBinary _ LazyAnd _ _ = Left "internal error: leftover &&"
+evalBinary _ LazyOr _ _ = Left "internal error: leftover ||"
 -- set operations
 evalBinary _ Contains (TermSet t) (TermSet t') = pure $ LBool (Set.isSubsetOf t' t)
 evalBinary _ Contains (TermSet t) t' = case valueToSetTerm t' of
     Just t'' -> pure $ LBool (Set.member t'' t)
     Nothing  -> Left "Sets cannot contain nested sets nor variables"
 evalBinary _ Contains (LString t) (LString t') = pure $ LBool (t' `isInfixOf` t)
+evalBinary _ Contains (TermArray t) t' = pure . LBool $ t' `elem` t
+evalBinary _ Contains (TermMap t) (LInteger i) = pure . LBool $ IntKey i `Map.member` t
+evalBinary _ Contains (TermMap t) (LString s) = pure . LBool $ StringKey s `Map.member` t
+evalBinary _ Contains (TermMap _) _ = pure $ LBool False
 evalBinary _ Contains _ _ = Left "Only sets and strings support `.contains()`"
 evalBinary _ Intersection (TermSet t) (TermSet t') = pure $ TermSet (Set.intersection t t')
 evalBinary _ Intersection _ _ = Left "Only sets support `.intersection()`"
 evalBinary _ Union (TermSet t) (TermSet t') = pure $ TermSet (Set.union t t')
 evalBinary _ Union _ _ = Left "Only sets support `.union()`"
+evalBinary _ Get (TermArray t) (LInteger i) = pure $
+  if i < List.genericLength t && i >= 0
+  then List.genericIndex t i
+  else LNull
+evalBinary _ Get (TermMap t) (LInteger i) = pure . fromMaybe LNull $ t !? IntKey i
+evalBinary _ Get (TermMap t) (LString s) = pure . fromMaybe LNull $ t !? StringKey s
+evalBinary _ Get _ _ = Left "Only arrays and maps support `.get()`"
+evalBinary _ Any _ _ = Left "internal error: leftover .any()"
+evalBinary _ All _ _ = Left "internal error: leftover .all()"
+evalBinary _ Try _ _ = Left "internal error: leftover .try_or()"
+evalBinary Limits{externFuncs} (BinaryFfi n) l r = runExternFunc externFuncs n l (Just r)
 
 checkedOp :: (Integer -> Integer -> Integer)
           -> Int64 -> Int64
@@ -466,6 +555,100 @@
   result <- Regex.execute regex text
   pure . LBool $ isJust result
 
+evaluateAll :: Limits
+            -> Bindings
+            -> Value
+            -> Expression
+            -> Either String Value
+evaluateAll l b xs' (EClosure [p] e) =
+  let runClosure v = do
+        if Map.member p b
+            then Left "Shadowed variable"
+            else Right ()
+        evaluateExpression l (Map.insert p v b) e >>= \case
+          LBool x -> Right x
+          _ -> Left "Expected boolean"
+      makeArray :: (MapKey, Value) -> Value
+      makeArray (k,v) = case k of
+        IntKey i    -> TermArray [LInteger i, v]
+        StringKey s -> TermArray [LString s, v]
+   in case xs' of
+    TermSet xs   -> LBool <$> allM (runClosure . setValueToValue) xs
+    TermArray xs -> LBool <$> allM runClosure xs
+    TermMap xs   -> LBool <$> allM (runClosure . makeArray) (Map.toList xs)
+    _            -> Left "Only sets, arrays and maps support .all()"
+evaluateAll _ _ _  _ = Left "Expected closure"
+
+evaluateAny :: Limits
+            -> Bindings
+            -> Value
+            -> Expression
+            -> Either String Value
+evaluateAny l b xs' (EClosure [p] e) =
+  let runClosure v = do
+        if Map.member p b
+            then Left "Shadowed variable"
+            else Right ()
+        evaluateExpression l (Map.insert p v b) e >>= \case
+          LBool x -> Right x
+          _ -> Left "Expected boolean"
+      makeArray :: (MapKey, Value) -> Value
+      makeArray (k,v) = case k of
+        IntKey i    -> TermArray [LInteger i, v]
+        StringKey s -> TermArray [LString s, v]
+   in case xs' of
+    TermSet xs   -> LBool <$> anyM (runClosure . setValueToValue) xs
+    TermArray xs -> LBool <$> anyM runClosure xs
+    TermMap xs   -> LBool <$> anyM (runClosure . makeArray) (Map.toList xs)
+    _            -> Left "Only sets, arrays and maps support .any()"
+evaluateAny _ _ _  _ = Left "Expected closure"
+
+evaluateLazyAnd :: Limits
+                -> Bindings
+                -> Value
+                -> Expression
+                -> Either String Value
+evaluateLazyAnd l b lhs' (EClosure [] e) =
+  let runClosure =
+        evaluateExpression l b e >>= \case
+          LBool x -> Right x
+          _ -> Left "Expected boolean"
+   in case lhs' of
+        LBool lhs -> if lhs
+                     then LBool <$> runClosure
+                     else Right $ LBool False
+        _ -> Left "Expected boolean"
+evaluateLazyAnd _ _ _  _ = Left "Expected closure"
+
+evaluateLazyOr :: Limits
+                -> Bindings
+                -> Value
+                -> Expression
+                -> Either String Value
+evaluateLazyOr l b lhs' (EClosure [] e) =
+  let runClosure =
+        evaluateExpression l b e >>= \case
+          LBool x -> Right x
+          _ -> Left "Expected boolean"
+   in case lhs' of
+        LBool lhs -> if lhs
+                     then Right $ LBool True
+                     else LBool <$> runClosure
+        _ -> Left "Expected boolean"
+evaluateLazyOr _ _ _  _ = Left "Expected closure"
+
+evaluateTry :: Limits
+            -> Bindings
+            -> Expression
+            -> Expression
+            -> Either String Value
+evaluateTry l b (EClosure [] e) e' = do
+  rhs <- evaluateExpression l b e'
+  case evaluateExpression l b e of
+    Right r -> Right r
+    Left _  -> Right rhs
+evaluateTry _ _ _ _                = Left "Expected closure"
+
 -- | Given bindings for variables, reduce an expression to a single
 -- datalog value
 evaluateExpression :: Limits
@@ -474,6 +657,19 @@
                    -> Either String Value
 evaluateExpression l b = \case
     EValue term -> applyVariable b term
-    EUnary op e' -> evalUnary op =<< evaluateExpression l b e'
-    EBinary op e' e'' -> uncurry (evalBinary l op) =<< join bitraverse (evaluateExpression l b) (e', e'')
-
+    EUnary op e -> evalUnary l op =<< evaluateExpression l b e
+    EBinary LazyAnd e e' -> do
+        lhs <- evaluateExpression l b e
+        evaluateLazyAnd l b lhs e'
+    EBinary LazyOr e e' -> do
+        lhs <- evaluateExpression l b e
+        evaluateLazyOr l b lhs e'
+    EBinary Any e e' -> do
+        lhs <- evaluateExpression l b e
+        evaluateAny l b lhs e'
+    EBinary All e e' -> do
+        lhs <- evaluateExpression l b e
+        evaluateAll l b lhs e'
+    EBinary Try e e' -> evaluateTry l b e e'
+    EBinary op e e' -> uncurry (evalBinary l op) =<< join bitraverse (evaluateExpression l b) (e, e')
+    EClosure _ _ -> Left "Unexpected closure"
diff --git a/src/Auth/Biscuit/Datalog/Parser.hs b/src/Auth/Biscuit/Datalog/Parser.hs
--- a/src/Auth/Biscuit/Datalog/Parser.hs
+++ b/src/Auth/Biscuit/Datalog/Parser.hs
@@ -9,6 +9,13 @@
 {-# LANGUAGE TemplateHaskellQuotes #-}
 {-# LANGUAGE TupleSections         #-}
 {-# LANGUAGE TypeApplications      #-}
+{-|
+  Module      : Auth.Biscuit.Datalog.AST
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+  Maintainer  : clement@delafargue.name
+  Parser for the authorization language
+-}
 module Auth.Biscuit.Datalog.Parser
   where
 
@@ -28,6 +35,7 @@
 import           Data.List.NonEmpty             (NonEmpty)
 import qualified Data.List.NonEmpty             as NE
 import           Data.Map.Strict                (Map)
+import qualified Data.Map.Strict                as Map
 import           Data.Maybe                     (isJust)
 import           Data.Set                       (Set)
 import qualified Data.Set                       as Set
@@ -51,7 +59,7 @@
 
 data SemanticError =
     VarInFact Span
-  | VarInSet  Span
+  | VarInCollection  Span
   | NestedSet Span
   | InvalidBs Text Span
   | InvalidPublicKey Text Span
@@ -62,7 +70,7 @@
 instance ShowErrorComponent SemanticError where
   showErrorComponent = \case
     VarInFact _            -> "Variables can't appear in a fact"
-    VarInSet  _            -> "Variables can't appear in a set"
+    VarInCollection  _     -> "Variables can't appear in a collection (set, array, map"
     NestedSet _            -> "Sets cannot be nested"
     InvalidBs e _          -> "Invalid bytestring literal: " <> T.unpack e
     InvalidPublicKey e _   -> "Invalid public key: " <> T.unpack e
@@ -93,9 +101,12 @@
   (sp, _) <- getSpan p
   registerError mkError sp
 
+identifierParser :: Parser Text
+identifierParser = takeWhile1P (Just "_, :, or any alphanumeric char") (\c -> c == '_' || c == ':' || isAlphaNum c)
+
 variableParser :: Parser Text
 variableParser =
-  C.char '$' *> takeWhile1P (Just "_, :, or any alphanumeric char") (\c -> c == '_' || c == ':' || isAlphaNum c)
+  C.char '$' *> identifierParser
 
 haskellVariableParser :: Parser Text
 haskellVariableParser = l $ do
@@ -107,37 +118,92 @@
   pure . maybe id T.cons leadingUS $ T.cons x xs
 
 setParser :: Parser (Set (Term' 'WithinSet 'InFact 'WithSlices))
-setParser = do
+setParser = choice [emptySetParser, nonEmptySetParser]
+
+emptySetParser :: Parser (Set (Term' 'WithinSet 'InFact 'WithSlices))
+emptySetParser = mempty <$ chunk "{,}"
+
+setTermParser :: Parser (Term' 'WithinSet 'InFact 'WithSlices)
+setTermParser = termParser
+  (forbid VarInCollection variableParser)
+  (forbid NestedSet setParser)
+  (forbid NestedSet arrayParser)
+  (forbid NestedSet mapParser)
+
+nonEmptySetParser :: Parser (Set (Term' 'WithinSet 'InFact 'WithSlices))
+nonEmptySetParser = do
+  _ <- l $ C.char '{'
+  ts <- sepBy1 setTermParser (l $ C.char ',')
+  _ <- l $ C.char '}'
+  pure $ Set.fromList ts
+
+valueParser :: Parser (Term' 'NotWithinSet 'InFact 'WithSlices)
+valueParser = termParser
+  (forbid VarInCollection variableParser)
+  setParser
+  arrayParser
+  mapParser
+
+arrayParser :: Parser [Term' 'NotWithinSet 'InFact 'WithSlices]
+arrayParser = do
   _ <- l $ C.char '['
-  ts <- sepBy (termParser (forbid VarInSet variableParser) (forbid NestedSet setParser)) (l $ C.char ',')
+  ts <- sepBy1 valueParser (l $ C.char ',')
   _ <- l $ C.char ']'
-  pure $ Set.fromList ts
+  pure ts
 
+mapEntryParser :: Parser (MapKey, Term' 'NotWithinSet 'InFact 'WithSlices)
+mapEntryParser = do
+  k <- l $ choice [ IntKey <$> intParser
+                  , StringKey <$> stringParser
+                  ]
+  _ <- l $ C.char ':'
+  v <- l valueParser
+  pure (k, v)
+
+mapParser :: Parser (Map MapKey (Term' 'NotWithinSet 'InFact 'WithSlices))
+mapParser = do
+  _ <- l $ C.char '{'
+  ts <- sepBy1 mapEntryParser (l $ C.char ',')
+  _ <- l $ C.char '}'
+  pure $ Map.fromList ts
+
 factTermParser :: Parser (Term' 'NotWithinSet 'InFact 'WithSlices)
 factTermParser = termParser (forbid VarInFact variableParser)
                             setParser
+                            arrayParser
+                            mapParser
 
 predicateTermParser :: Parser (Term' 'NotWithinSet 'InPredicate 'WithSlices)
 predicateTermParser = termParser variableParser
                                  setParser
+                                 arrayParser
+                                 mapParser
 
 termParser :: Parser (VariableType inSet pof)
            -> Parser (SetType inSet 'WithSlices)
+           -> Parser (ArrayType inSet 'WithSlices)
+           -> Parser (MapType inSet 'WithSlices)
            -> Parser (Term' inSet pof 'WithSlices)
-termParser parseVar parseSet = l $ choice
-  [ Antiquote . Slice <$> haskellVariableParser <?> "parameter (eg. {paramName})"
+termParser parseVar parseSet parseArray parseMap = l $ choice
+  [ TermSet <$> try parseSet <?> "set (eg. {1,2,3})"
+  , TermArray <$> try parseArray <?> "array (eg. [1, 2, 3])"
+  , TermMap <$> try parseMap <?> "map (eg. {\"key\": 1})"
+  , Antiquote . Slice <$> haskellVariableParser <?> "parameter (eg. {paramName})"
   , Variable <$> parseVar <?> "datalog variable (eg. $variable)"
-  , TermSet <$> parseSet <?> "set (eg. [1,2,3])"
   , LBytes <$> (chunk "hex:" *> hexParser) <?> "hex-encoded bytestring (eg. hex:00ff99)"
   , LDate <$> rfc3339DateParser <?> "RFC3339-formatted timestamp (eg. 2022-11-29T00:00:00Z)"
   , LInteger <$> intParser <?> "(signed) integer"
-  , LString . T.pack <$> (C.char '"' *> manyTill L.charLiteral (C.char '"')) <?> "string literal"
+  , LString <$> stringParser <?> "string literal"
   , LBool <$> choice [ True <$ chunk "true"
                      , False <$ chunk "false"
                      ]
           <?> "boolean value (eg. true or false)"
+  , LNull <$ chunk "null" <?> "null value"
   ]
 
+stringParser :: Parser Text
+stringParser = T.pack <$> (C.char '"' *> manyTill L.charLiteral (C.char '"'))
+
 intParser :: Parser Int64
 intParser = do
   integer :: Integer <- L.signed C.space L.decimal <?> "(signed) integer"
@@ -226,6 +292,13 @@
 predicateParser :: Parser (Predicate' 'InPredicate 'WithSlices)
 predicateParser = predicateParser' predicateTermParser
 
+closureParser :: Parser (Text, Expression' 'WithSlices)
+closureParser = do
+    param <- l variableParser
+    _ <- l $ chunk "->"
+    body <- l expressionParser
+    pure (param, body)
+
 expressionParser :: Parser (Expression' 'WithSlices)
 expressionParser =
   let base = choice [ try methodsParser
@@ -235,9 +308,12 @@
 
 table :: [[Expr.Operator Parser (Expression' 'WithSlices)]]
 table =
-  let infixL name op = Expr.InfixL (EBinary op <$ l (chunk name) <?> "infix operator")
-      infixN name op = Expr.InfixN (EBinary op <$ l (chunk name) <?> "infix operator")
+  let infixL name op = Expr.InfixL (makeBinary op <$ l (chunk name) <?> "infix operator")
+      infixN name op = Expr.InfixN (makeBinary op <$ l (chunk name) <?> "infix operator")
       prefix name op = Expr.Prefix (EUnary op <$  l (chunk name) <?> "prefix operator")
+      makeBinary LazyOr e e'  = EBinary LazyOr e (EClosure [] e')
+      makeBinary LazyAnd e e' = EBinary LazyAnd e (EClosure [] e')
+      makeBinary op e e'      = EBinary op e e'
    in [ [ prefix "!" Negate]
       , [ infixL  "*" Mul
         , infixL  "/" Div
@@ -254,11 +330,13 @@
         , infixN  ">=" GreaterOrEqual
         , infixN  "<"  LessThan
         , infixN  ">"  GreaterThan
-        , infixN  "==" Equal
-        , infixN  "!=" NotEqual
+        , infixN  "===" Equal
+        , infixN  "!==" NotEqual
+        , infixN  "==" HeterogeneousEqual
+        , infixN  "!=" HeterogeneousNotEqual
         ]
-      , [ infixL "&&" And ]
-      , [ infixL "||" Or ]
+      , [ infixL "&&" LazyAnd ]
+      , [ infixL "||" LazyOr ]
       ]
 
 binaryMethodParser :: Parser (Expression' 'WithSlices -> Expression' 'WithSlices)
@@ -271,16 +349,30 @@
     , Prefix       <$ chunk "starts_with"
     , Suffix       <$ chunk "ends_with"
     , Regex        <$ chunk "matches"
+    , Any          <$ chunk "any"
+    , All          <$ chunk "all"
+    , Get          <$ chunk "get"
+    , BinaryFfi    <$> (chunk "extern::" *> identifierParser)
+    , Try          <$ chunk "try_or"
     ]
   _ <- l $ C.char '('
-  e2 <- l expressionParser
+  e2 <- case method of
+    Any -> uncurry EClosure . first pure <$> l closureParser
+    All -> uncurry EClosure . first pure <$> l closureParser
+    _   -> l expressionParser
   _ <- l $ C.char ')'
-  pure $ \e1 -> EBinary method e1 e2
+  pure $ \e1 -> case method of
+    Try -> EBinary method (EClosure [] e1) e2
+    _   -> EBinary method e1 e2
 
 unaryMethodParser :: Parser (Expression' 'WithSlices -> Expression' 'WithSlices)
 unaryMethodParser = do
   _ <- C.char '.'
-  method <- Length <$ chunk "length"
+  method <- choice
+            [ Length <$ chunk "length"
+            , TypeOf <$ chunk "type"
+            , UnaryFfi <$> (chunk "extern::" *> identifierParser)
+            ]
   _ <- l $ chunk "()"
   pure $ EUnary method
 
@@ -355,8 +447,9 @@
 
 checkParser :: Bool -> Parser (Check' 'Repr 'WithSlices)
 checkParser inAuthorizer = do
-  cKind <- l $ choice [ One <$ chunk "check if"
-                      , All <$ chunk "check all"
+  cKind <- l $ choice [ CheckOne <$ chunk "check if"
+                      , CheckAll <$ chunk "check all"
+                      , Reject <$ chunk "reject if"
                       ]
   cQueries <- queryParser inAuthorizer
   pure Check{..}
diff --git a/src/Auth/Biscuit/Datalog/ScopedExecutor.hs b/src/Auth/Biscuit/Datalog/ScopedExecutor.hs
--- a/src/Auth/Biscuit/Datalog/ScopedExecutor.hs
+++ b/src/Auth/Biscuit/Datalog/ScopedExecutor.hs
@@ -8,6 +8,13 @@
 {-# LANGUAGE QuasiQuotes                #-}
 {-# LANGUAGE RecordWildCards            #-}
 {-# LANGUAGE TupleSections              #-}
+{-|
+  Module      : Auth.Biscuit.Datalog.Executor
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+  Maintainer  : clement@delafargue.name
+  Wrapper for the executor engine that makes sure facts are properly scoped
+-}
 module Auth.Biscuit.Datalog.ScopedExecutor
   ( BlockWithRevocationId
   , runAuthorizer
diff --git a/src/Auth/Biscuit/Example.hs b/src/Auth/Biscuit/Example.hs
--- a/src/Auth/Biscuit/Example.hs
+++ b/src/Auth/Biscuit/Example.hs
@@ -1,5 +1,12 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes       #-}
+{-|
+  Module      : Auth.Biscuit.Example
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+  Maintainer  : clement@delafargue.name
+  Working examples of biscuit-haskell
+-}
 module Auth.Biscuit.Example where
 
 import           Data.ByteString (ByteString)
@@ -23,7 +30,7 @@
   let authority = [block|
        // this is a comment
        right("file1", {allowedOperations});
-       check if source_ip($source_ip), ["127.0.0.1", {networkLocal}].contains($source_ip);
+       check if source_ip($source_ip), {"127.0.0.1", {networkLocal}}.contains($source_ip);
        |]
   biscuit <- mkBiscuit privateKey' authority
   let block1 = [block|check if time($time), $time < 2025-05-08T00:00:00Z;|]
diff --git a/src/Auth/Biscuit/Proto.hs b/src/Auth/Biscuit/Proto.hs
--- a/src/Auth/Biscuit/Proto.hs
+++ b/src/Auth/Biscuit/Proto.hs
@@ -6,7 +6,7 @@
 {-|
   Module      : Auth.Biscuit.Proto
   Copyright   : © Clément Delafargue, 2021
-  License     : MIT
+  License     : BSD-3-Clause
   Maintainer  : clement@delafargue.name
   Haskell data structures mapping the biscuit protobuf definitions
 -}
@@ -21,21 +21,25 @@
   , Block (..)
   , Scope (..)
   , ScopeType (..)
-  , FactV2 (..)
-  , RuleV2 (..)
+  , Fact (..)
+  , Rule (..)
   , CheckKind (..)
-  , CheckV2 (..)
-  , PredicateV2 (..)
-  , TermV2 (..)
-  , ExpressionV2 (..)
+  , Check (..)
+  , Predicate (..)
+  , Term (..)
+  , Expression (..)
   , TermSet (..)
+  , TermArray (..)
+  , TermMap (..)
+  , MapKey (..)
+  , MapEntry (..)
+  , Empty (..)
   , Op (..)
   , OpUnary (..)
   , UnaryKind (..)
   , OpBinary (..)
   , BinaryKind (..)
-  , OpTernary (..)
-  , TernaryKind (..)
+  , OpClosure (..)
   , ThirdPartyBlockContents (..)
   , ThirdPartyBlockRequest (..)
   , getField
@@ -83,6 +87,7 @@
   , nextKey     :: Required 2 (Message PublicKey)
   , signature   :: Required 3 (Value ByteString)
   , externalSig :: Optional 4 (Message ExternalSig)
+  , version     :: Optional 5 (Value Int32)
   }
   deriving (Generic, Show)
   deriving anyclass (Decode, Encode)
@@ -98,14 +103,14 @@
   deriving anyclass (Decode, Encode)
 
 data Block = Block {
-    symbols   :: Repeated 1 (Value Text)
-  , context   :: Optional 2 (Value Text)
-  , version   :: Optional 3 (Value Int32)
-  , facts_v2  :: Repeated 4 (Message FactV2)
-  , rules_v2  :: Repeated 5 (Message RuleV2)
-  , checks_v2 :: Repeated 6 (Message CheckV2)
-  , scope     :: Repeated 7 (Message Scope)
-  , pksTable  :: Repeated 8 (Message PublicKey)
+    symbols  :: Repeated 1 (Value Text)
+  , context  :: Optional 2 (Value Text)
+  , version  :: Optional 3 (Value Int32)
+  , facts    :: Repeated 4 (Message Fact)
+  , rules    :: Repeated 5 (Message Rule)
+  , checks   :: Repeated 6 (Message Check)
+  , scope    :: Repeated 7 (Message Scope)
+  , pksTable :: Repeated 8 (Message PublicKey)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
@@ -120,70 +125,102 @@
     deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
-newtype FactV2 = FactV2
-  { predicate :: Required 1 (Message PredicateV2)
+newtype Fact = Fact
+  { predicate :: Required 1 (Message Predicate)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
-data RuleV2 = RuleV2
-  { head        :: Required 1 (Message PredicateV2)
-  , body        :: Repeated 2 (Message PredicateV2)
-  , expressions :: Repeated 3 (Message ExpressionV2)
+data Rule = Rule
+  { head        :: Required 1 (Message Predicate)
+  , body        :: Repeated 2 (Message Predicate)
+  , expressions :: Repeated 3 (Message Expression)
   , scope       :: Repeated 4 (Message Scope)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
 data CheckKind =
-    One
-  | All
+    CheckOne
+  | CheckAll
+  | Reject
   deriving stock (Show, Enum, Bounded)
 
-data CheckV2 = CheckV2
-  { queries :: Repeated 1 (Message RuleV2)
+data Check = Check
+  { queries :: Repeated 1 (Message Rule)
   , kind    :: Optional 2 (Enumeration CheckKind)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
-data PredicateV2 = PredicateV2
+data Predicate = Predicate
   { name  :: Required 1 (Value Int64)
-  , terms :: Repeated 2 (Message TermV2)
+  , terms :: Repeated 2 (Message Term)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
-data TermV2 =
-    TermVariable (Required 1 (Value Int64))
-  | TermInteger  (Required 2 (Value Int64))
-  | TermString   (Required 3 (Value Int64))
-  | TermDate     (Required 4 (Value Int64))
-  | TermBytes    (Required 5 (Value ByteString))
-  | TermBool     (Required 6 (Value Bool))
-  | TermTermSet  (Required 7 (Message TermSet))
+data Term =
+    TermVariable  (Required 1  (Value Int64))
+  | TermInteger   (Required 2  (Value Int64))
+  | TermString    (Required 3  (Value Int64))
+  | TermDate      (Required 4  (Value Int64))
+  | TermBytes     (Required 5  (Value ByteString))
+  | TermBool      (Required 6  (Value Bool))
+  | TermTermSet   (Required 7  (Message TermSet))
+  | TermNull      (Required 8  (Message Empty))
+  | TermTermArray (Required 9  (Message TermArray))
+  | TermTermMap   (Required 10 (Message TermMap))
     deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
+data Empty = Empty {}
+    deriving stock (Generic, Show)
+    deriving anyclass (Decode, Encode)
 
+
 newtype TermSet = TermSet
-  { set :: Repeated 1 (Message TermV2)
+  { set :: Repeated 1 (Message Term)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
-newtype ExpressionV2 = ExpressionV2
+newtype TermArray = TermArray
+  { array :: Repeated 1 (Message Term)
+  } deriving stock (Generic, Show)
+    deriving anyclass (Decode, Encode)
+
+data MapKey =
+    MapKeyInt    (Required 1 (Value Int64))
+  | MapKeyString (Required 2 (Value Int64))
+    deriving stock (Generic, Show)
+    deriving anyclass (Decode, Encode)
+
+data MapEntry = MapEntry
+  { key   ::   Required 1 (Message MapKey)
+  , value :: Required 2 (Message Term)
+  } deriving stock (Generic, Show)
+    deriving anyclass (Decode, Encode)
+
+newtype TermMap = TermMap
+  { map :: Repeated 1 (Message MapEntry)
+  } deriving stock (Generic, Show)
+    deriving anyclass (Decode, Encode)
+
+newtype Expression = Expression
   { ops :: Repeated 1 (Message Op)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
 data Op =
-    OpVValue  (Required 1 (Message TermV2))
+    OpVValue  (Required 1 (Message Term))
   | OpVUnary  (Required 2 (Message OpUnary))
   | OpVBinary (Required 3 (Message OpBinary))
+  | OpVClosure (Required 4 (Message OpClosure))
     deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
-data UnaryKind = Negate | Parens | Length
+data UnaryKind = Negate | Parens | Length | TypeOf | UnaryFfi
   deriving stock (Show, Enum, Bounded)
 
-newtype OpUnary = OpUnary
-  { kind :: Required 1 (Enumeration UnaryKind)
+data OpUnary = OpUnary
+  { kind    :: Required 1 (Enumeration UnaryKind)
+  , ffiName :: Optional 2 (Value Int64)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
@@ -209,19 +246,26 @@
   | BitwiseOr
   | BitwiseXor
   | NotEqual
+  | HeterogeneousEqual
+  | HeterogeneousNotEqual
+  | LazyAnd
+  | LazyOr
+  | All
+  | Any
+  | Get
+  | BinaryFfi
+  | TryOr
   deriving stock (Show, Enum, Bounded)
 
-newtype OpBinary = OpBinary
-  { kind :: Required 1 (Enumeration BinaryKind)
+data OpBinary = OpBinary
+  { kind    :: Required 1 (Enumeration BinaryKind)
+  , ffiName :: Optional 2 (Value Int64)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
-data TernaryKind =
-    VerifyEd25519Signature
-  deriving stock (Show, Enum, Bounded)
-
-newtype OpTernary = OpTernary
-  { kind :: Required 1 (Enumeration TernaryKind)
+data OpClosure = OpClosure
+  { params :: Repeated 1 (Value Int64)
+  , ops    :: Repeated 2 (Message Op)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
@@ -253,8 +297,9 @@
 
 data ThirdPartyBlockRequest
   = ThirdPartyBlockRequest
-  { previousPk :: Required 1 (Message PublicKey)
-  , pkTable    :: Repeated 2 (Message PublicKey)
+  { legacyPk :: Optional 1 (Message PublicKey)
+  , pkTable  :: Repeated 2 (Message PublicKey)
+  , prevSig  :: Required 3 (Value ByteString)
   } deriving stock (Generic, Show)
     deriving anyclass (Decode, Encode)
 
diff --git a/src/Auth/Biscuit/ProtoBufAdapter.hs b/src/Auth/Biscuit/ProtoBufAdapter.hs
--- a/src/Auth/Biscuit/ProtoBufAdapter.hs
+++ b/src/Auth/Biscuit/ProtoBufAdapter.hs
@@ -8,7 +8,7 @@
 {-|
   Module      : Auth.Biscuit.Utils
   Copyright   : © Clément Delafargue, 2021
-  License     : MIT
+  License     : BSD-3-Clause
   Maintainer  : clement@delafargue.name
   Conversion functions between biscuit components and protobuf-encoded components
 -}
@@ -28,9 +28,11 @@
 
 import           Control.Monad            (unless, when)
 import           Control.Monad.State      (StateT, get, lift, modify)
+import           Data.Bitraversable       (bitraverse)
 import           Data.ByteString          (ByteString)
 import           Data.Int                 (Int64)
 import qualified Data.List.NonEmpty       as NE
+import qualified Data.Map                 as Map
 import           Data.Maybe               (isJust, isNothing)
 import qualified Data.Set                 as Set
 import qualified Data.Text                as T
@@ -72,10 +74,12 @@
   let sig = Crypto.signature $ PB.getField signature
   mSig <- traverse pbToOptionalSignature $ PB.getField externalSig
   pk  <- pbToPublicKey $ PB.getField nextKey
+  let sigVersion = fromIntegral <$> PB.getField version
   pure ( PB.getField block
        , sig
        , pk
        , mSig
+       , sigVersion
        )
 
 publicKeyToPb :: Crypto.PublicKey -> PB.PublicKey
@@ -91,11 +95,12 @@
   }
 
 signedBlockToPb :: Crypto.SignedBlock -> PB.SignedBlock
-signedBlockToPb (block, sig, pk, eSig) = PB.SignedBlock
+signedBlockToPb (block, sig, pk, eSig, sigVersion) = PB.SignedBlock
   { block = PB.putField block
   , signature = PB.putField $ Crypto.sigBytes sig
   , nextKey = PB.putField $ publicKeyToPb pk
   , externalSig = PB.putField $ externalSigToPb <$> eSig
+  , version = PB.putField $ fromIntegral <$> sigVersion
   }
 
 pbToProof :: PB.Proof -> Either String (Either Crypto.Signature Crypto.SecretKey)
@@ -125,11 +130,17 @@
       bVersion = PB.getField version
   lift $ do
     let s = symbolsForCurrentBlock
-    bFacts <- traverse (pbToFact s) $ PB.getField facts_v2
-    bRules <- traverse (pbToRule s) $ PB.getField rules_v2
-    bChecks <- traverse (pbToCheck s) $ PB.getField checks_v2
+    bFacts <- traverse (pbToFact s) $ PB.getField facts
+    bRules <- traverse (pbToRule s) $ PB.getField rules
+    bChecks <- traverse (pbToCheck s) $ PB.getField checks
     bScope <- Set.fromList <$> traverse (pbToScope s) (PB.getField scope)
-    let v5Plus = isJust ePk
+    let v6Plus = or
+          [ any isReject bChecks
+          , not (all predicateHasNoV6Values bFacts)
+          , not (all ruleHasNoV6Values bRules)
+          , not (all checkHasNoV6Values bChecks)
+          ]
+        v5Plus = isJust ePk
         v4Plus = not $ and
           [ Set.null bScope
           , all ruleHasNoScope bRules
@@ -138,22 +149,29 @@
           , all ruleHasNoV4Operators bRules
           , all (queryHasNoV4Operators . cQueries) bChecks
           ]
-    case (bVersion, v4Plus, v5Plus) of
-      (Just 5, _, _) -> pure Block {..}
-      (Just 4, _, False) -> pure Block {..}
-      (Just 4, _, True) ->
+    case (bVersion, v4Plus, v5Plus, v6Plus) of
+      (Just 6, _, _, _) -> pure Block {..}
+      (Just 5, _, _, True) ->
+        Left "Biscuit v6 features are present, but the block version is 5."
+      (Just 5, _, _, _) -> pure Block {..}
+      (Just 4, _, False, False) -> pure Block {..}
+      (Just 4, _, _, True) ->
+        Left "Biscuit v6 features are present, but the block version is 4."
+      (Just 4, _, True, False) ->
         Left "Biscuit v5 features are present, but the block version is 4."
-      (Just 3, False, False) -> pure Block {..}
-      (Just 3, True, False) ->
+      (Just 3, False, False, False) -> pure Block {..}
+      (Just 3, True, False, False) ->
         Left "Biscuit v4 features are present, but the block version is 3."
-      (Just 3, _, True) ->
+      (Just 3, _, True, False) ->
         Left "Biscuit v5 features are present, but the block version is 3."
+      (Just 3, _, _, True) ->
+        Left "Biscuit v6 features are present, but the block version is 3."
       _ ->
-        Left $ "Unsupported biscuit version: " <> maybe "0" show bVersion <> ". Only versions 3 and 4 are supported"
+        Left $ "Unsupported biscuit version: " <> maybe "0" show bVersion <> ". Only versions 3 to 6 are supported"
 
 -- | Turn a biscuit block into a protobuf block, for serialization,
 -- along with the newly defined symbols
-blockToPb :: Bool -> Symbols -> Block -> (BlockSymbols, PB.Block)
+blockToPb :: Bool -> Symbols -> Block -> ((BlockSymbols, Int), PB.Block)
 blockToPb hasExternalPk existingSymbols b@Block{..} =
   let v4Plus = not $ and
         [Set.null bScope
@@ -164,38 +182,45 @@
         , all (queryHasNoV4Operators . cQueries) bChecks
         ]
       v5Plus = hasExternalPk
+      v6Plus = or
+        [ any isReject bChecks
+        , not (all predicateHasNoV6Values bFacts)
+        , not (all ruleHasNoV6Values bRules)
+        , not (all checkHasNoV6Values bChecks)
+        ]
       bSymbols = buildSymbolTable existingSymbols b
       s = reverseSymbols $ addFromBlock existingSymbols bSymbols
-      symbols   = PB.putField $ getSymbolList bSymbols
-      context   = PB.putField bContext
-      facts_v2  = PB.putField $ factToPb s <$> bFacts
-      rules_v2  = PB.putField $ ruleToPb s <$> bRules
-      checks_v2 = PB.putField $ checkToPb s <$> bChecks
-      scope     = PB.putField $ scopeToPb s <$> Set.toList bScope
-      pksTable   = PB.putField $ publicKeyToPb <$> getPkList bSymbols
-      version   = PB.putField $ if | v5Plus    -> Just 5
-                                   | v4Plus    -> Just 4
-                                   | otherwise -> Just 3
-   in (bSymbols, PB.Block {..})
+      symbols  = PB.putField $ getSymbolList bSymbols
+      context  = PB.putField bContext
+      facts    = PB.putField $ factToPb s <$> bFacts
+      rules    = PB.putField $ ruleToPb s <$> bRules
+      checks   = PB.putField $ checkToPb s <$> bChecks
+      scope    = PB.putField $ scopeToPb s <$> Set.toList bScope
+      pksTable = PB.putField $ publicKeyToPb <$> getPkList bSymbols
+      version  =  if | v6Plus    -> 6
+                     | v5Plus    -> 5
+                     | v4Plus    -> 4
+                     | otherwise -> 3
+   in ((bSymbols, version), PB.Block {version = PB.putField $ Just $ fromIntegral version, ..})
 
-pbToFact :: Symbols -> PB.FactV2 -> Either String Fact
-pbToFact s PB.FactV2{predicate} = do
+pbToFact :: Symbols -> PB.Fact -> Either String Fact
+pbToFact s PB.Fact{predicate} = do
   let pbName  = PB.getField $ PB.name  $ PB.getField predicate
       pbTerms = PB.getField $ PB.terms $ PB.getField predicate
   name <- getSymbol s $ SymbolRef pbName
   terms <- traverse (pbToValue s) pbTerms
   pure Predicate{..}
 
-factToPb :: ReverseSymbols -> Fact -> PB.FactV2
+factToPb :: ReverseSymbols -> Fact -> PB.Fact
 factToPb s Predicate{..} =
   let
-      predicate = PB.PredicateV2
+      predicate = PB.Predicate
         { name  = PB.putField $ getSymbolRef $ getSymbolCode s name
         , terms = PB.putField $ valueToPb s <$> terms
         }
-   in PB.FactV2{predicate = PB.putField predicate}
+   in PB.Fact{predicate = PB.putField predicate}
 
-pbToRule :: Symbols -> PB.RuleV2 -> Either String Rule
+pbToRule :: Symbols -> PB.Rule -> Either String Rule
 pbToRule s pbRule = do
   let pbHead = PB.getField $ PB.head pbRule
       pbBody = PB.getField $ PB.body pbRule
@@ -209,27 +234,28 @@
     Failure vs -> Left $ "Unbound variables in rule: " <> T.unpack (T.intercalate ", " $ NE.toList vs)
     Success r  -> pure r
 
-ruleToPb :: ReverseSymbols -> Rule -> PB.RuleV2
+ruleToPb :: ReverseSymbols -> Rule -> PB.Rule
 ruleToPb s Rule{..} =
-  PB.RuleV2
+  PB.Rule
     { head = PB.putField $ predicateToPb s rhead
     , body = PB.putField $ predicateToPb s <$> body
     , expressions = PB.putField $ expressionToPb s <$> expressions
     , scope = PB.putField $ scopeToPb s <$> Set.toList scope
     }
 
-pbToCheck :: Symbols -> PB.CheckV2 -> Either String Check
-pbToCheck s PB.CheckV2{queries,kind} = do
+pbToCheck :: Symbols -> PB.Check -> Either String Check
+pbToCheck s PB.Check{queries,kind} = do
   let toCheck Rule{body,expressions,scope} = QueryItem{qBody = body, qExpressions = expressions, qScope = scope}
   rules <- traverse (pbToRule s) $ PB.getField queries
   let cQueries = toCheck <$> rules
   let cKind = case PB.getField kind of
-        Just PB.All -> All
-        Just PB.One -> One
-        Nothing     -> One
+        Just PB.CheckAll -> CheckAll
+        Just PB.CheckOne -> CheckOne
+        Just PB.Reject   -> Reject
+        Nothing          -> CheckOne
   pure Check{..}
 
-checkToPb :: ReverseSymbols -> Check -> PB.CheckV2
+checkToPb :: ReverseSymbols -> Check -> PB.Check
 checkToPb s Check{..} =
   let dummyHead = Predicate "query" []
       toQuery QueryItem{..} =
@@ -239,9 +265,10 @@
                           , scope = qScope
                           }
       pbKind = case cKind of
-        One -> Nothing
-        All -> Just PB.All
-   in PB.CheckV2 { queries = PB.putField $ toQuery <$> cQueries
+        CheckOne -> Nothing
+        CheckAll -> Just PB.CheckAll
+        Reject   -> Just PB.Reject
+   in PB.Check { queries = PB.putField $ toQuery <$> cQueries
                  , kind = PB.putField pbKind
                  }
 
@@ -259,7 +286,7 @@
   Previous      -> PB.ScType $ PB.putField PB.ScopePrevious
   BlockId pk    -> PB.ScBlock $ PB.putField $ getPublicKeyCode s pk
 
-pbToPredicate :: Symbols -> PB.PredicateV2 -> Either String (Predicate' 'InPredicate 'Representation)
+pbToPredicate :: Symbols -> PB.Predicate -> Either String (Predicate' 'InPredicate 'Representation)
 pbToPredicate s pbPredicate = do
   let pbName  = PB.getField $ PB.name  pbPredicate
       pbTerms = PB.getField $ PB.terms pbPredicate
@@ -267,9 +294,9 @@
   terms <- traverse (pbToTerm s) pbTerms
   pure Predicate{..}
 
-predicateToPb :: ReverseSymbols -> Predicate -> PB.PredicateV2
+predicateToPb :: ReverseSymbols -> Predicate -> PB.Predicate
 predicateToPb s Predicate{..} =
-  PB.PredicateV2
+  PB.Predicate
     { name  = PB.putField $ getSymbolRef $ getSymbolCode s name
     , terms = PB.putField $ termToPb s <$> terms
     }
@@ -277,7 +304,7 @@
 pbTimeToUtcTime :: Int64 -> UTCTime
 pbTimeToUtcTime = posixSecondsToUTCTime . fromIntegral
 
-pbToTerm :: Symbols -> PB.TermV2 -> Either String Term
+pbToTerm :: Symbols -> PB.Term -> Either String Term
 pbToTerm s = \case
   PB.TermInteger  f -> pure $ LInteger $ fromIntegral $ PB.getField f
   PB.TermString   f ->        LString <$> getSymbol s (SymbolRef $ PB.getField f)
@@ -286,8 +313,11 @@
   PB.TermBool     f -> pure $ LBool    $ PB.getField f
   PB.TermVariable f -> Variable <$> getSymbol s (SymbolRef $ PB.getField f)
   PB.TermTermSet  f -> TermSet . Set.fromList <$> traverse (pbToSetValue s) (PB.getField . PB.set $ PB.getField f)
+  PB.TermTermArray f -> TermArray <$> traverse (pbToValue s) (PB.getField . PB.array $ PB.getField f)
+  PB.TermTermMap f -> TermMap . Map.fromList <$> traverse (pbToMapEntry s) (PB.getField . PB.map $ PB.getField f)
+  PB.TermNull     _ -> pure LNull
 
-termToPb :: ReverseSymbols -> Term -> PB.TermV2
+termToPb :: ReverseSymbols -> Term -> PB.Term
 termToPb s = \case
   Variable n -> PB.TermVariable $ PB.putField $ getSymbolRef $ getSymbolCode s n
   LInteger v -> PB.TermInteger  $ PB.putField $ fromIntegral v
@@ -296,10 +326,29 @@
   LBytes   v -> PB.TermBytes    $ PB.putField v
   LBool    v -> PB.TermBool     $ PB.putField v
   TermSet vs -> PB.TermTermSet  $ PB.putField $ PB.TermSet $ PB.putField $ setValueToPb s <$> Set.toList vs
+  TermArray vs -> PB.TermTermArray $ PB.putField $ PB.TermArray $ PB.putField $ valueToPb s <$> vs
+  TermMap vs -> PB.TermTermMap $ PB.putField $ PB.TermMap $ PB.putField $ uncurry (mapEntryToPb s) <$> Map.toList vs
+  LNull      -> PB.TermNull     $ PB.putField $ PB.Empty {}
 
   Antiquote v -> absurd v
 
-pbToValue :: Symbols -> PB.TermV2 -> Either String Value
+mapEntryToPb :: ReverseSymbols -> MapKey -> Value -> PB.MapEntry
+mapEntryToPb s k v = PB.MapEntry
+  { key = PB.putField $ case k of
+      IntKey i    -> PB.MapKeyInt . PB.putField $ fromIntegral i
+      StringKey n -> PB.MapKeyString . PB.putField $ getSymbolRef $ getSymbolCode s n
+  , value = PB.putField $ valueToPb s v
+  }
+
+pbToMapEntry :: Symbols -> PB.MapEntry -> Either String (MapKey, Value)
+pbToMapEntry s PB.MapEntry{key,value} = do
+  k <- case PB.getField key of
+         PB.MapKeyInt i    -> pure . IntKey . fromIntegral $ PB.getField i
+         PB.MapKeyString i -> StringKey <$> getSymbol s (SymbolRef $ PB.getField i)
+  v <- pbToValue s $ PB.getField value
+  pure (k, v)
+
+pbToValue :: Symbols -> PB.Term -> Either String Value
 pbToValue s = \case
   PB.TermInteger  f -> pure $ LInteger $ fromIntegral $ PB.getField f
   PB.TermString   f ->        LString <$> getSymbol s (SymbolRef $ PB.getField f)
@@ -308,8 +357,11 @@
   PB.TermBool     f -> pure $ LBool    $ PB.getField f
   PB.TermVariable _ -> Left "Variables can't appear in facts"
   PB.TermTermSet  f -> TermSet . Set.fromList <$> traverse (pbToSetValue s) (PB.getField . PB.set $ PB.getField f)
+  PB.TermTermArray f -> TermArray <$> traverse (pbToValue s) (PB.getField . PB.array $ PB.getField f)
+  PB.TermTermMap f -> TermMap . Map.fromList <$> traverse (pbToMapEntry s) (PB.getField . PB.map $ PB.getField f)
+  PB.TermNull     _ -> pure LNull
 
-valueToPb :: ReverseSymbols -> Value -> PB.TermV2
+valueToPb :: ReverseSymbols -> Value -> PB.Term
 valueToPb s = \case
   LInteger v -> PB.TermInteger $ PB.putField $ fromIntegral v
   LString  v -> PB.TermString  $ PB.putField $ getSymbolRef $ getSymbolCode s v
@@ -317,123 +369,191 @@
   LBytes   v -> PB.TermBytes   $ PB.putField v
   LBool    v -> PB.TermBool    $ PB.putField v
   TermSet vs -> PB.TermTermSet $ PB.putField $ PB.TermSet $ PB.putField $ setValueToPb s <$> Set.toList vs
+  TermArray vs -> PB.TermTermArray $ PB.putField $ PB.TermArray $ PB.putField $ valueToPb s <$> vs
+  TermMap vs -> PB.TermTermMap $ PB.putField $ PB.TermMap $ PB.putField $ uncurry (mapEntryToPb s) <$> Map.toList vs
+  LNull      -> PB.TermNull $ PB.putField PB.Empty
 
   Variable v  -> absurd v
   Antiquote v -> absurd v
 
-pbToSetValue :: Symbols -> PB.TermV2 -> Either String (Term' 'WithinSet 'InFact 'Representation)
+pbToSetValue :: Symbols -> PB.Term -> Either String (Term' 'WithinSet 'InFact 'Representation)
 pbToSetValue s = \case
   PB.TermInteger  f -> pure $ LInteger $ fromIntegral $ PB.getField f
   PB.TermString   f ->        LString  <$> getSymbol s (SymbolRef $ PB.getField f)
   PB.TermDate     f -> pure $ LDate    $ pbTimeToUtcTime $ PB.getField f
   PB.TermBytes    f -> pure $ LBytes   $ PB.getField f
   PB.TermBool     f -> pure $ LBool    $ PB.getField f
+  PB.TermNull     _ -> pure LNull
   PB.TermVariable _ -> Left "Variables can't appear in facts or sets"
   PB.TermTermSet  _ -> Left "Sets can't be nested"
+  PB.TermTermArray _ -> Left "Arrays can’t appear in sets"
+  PB.TermTermMap _ -> Left "Maps can’t appear in sets"
 
-setValueToPb :: ReverseSymbols -> Term' 'WithinSet 'InFact 'Representation -> PB.TermV2
+setValueToPb :: ReverseSymbols -> Term' 'WithinSet 'InFact 'Representation -> PB.Term
 setValueToPb s = \case
   LInteger v  -> PB.TermInteger $ PB.putField $ fromIntegral v
   LString  v  -> PB.TermString  $ PB.putField $ getSymbolRef $ getSymbolCode s v
   LDate    v  -> PB.TermDate    $ PB.putField $ round $ utcTimeToPOSIXSeconds v
   LBytes   v  -> PB.TermBytes   $ PB.putField v
   LBool    v  -> PB.TermBool    $ PB.putField v
+  LNull      -> PB.TermNull     $ PB.putField $ PB.Empty {}
 
   TermSet   v -> absurd v
+  TermArray v -> absurd v
+  TermMap   v -> absurd v
   Variable  v -> absurd v
   Antiquote v -> absurd v
 
-pbToExpression :: Symbols -> PB.ExpressionV2 -> Either String Expression
-pbToExpression s PB.ExpressionV2{ops} = do
+pbToExpression :: Symbols -> PB.Expression -> Either String Expression
+pbToExpression s PB.Expression{ops} = do
   parsedOps <- traverse (pbToOp s) $ PB.getField ops
   fromStack parsedOps
 
-expressionToPb :: ReverseSymbols -> Expression -> PB.ExpressionV2
+expressionToPb :: ReverseSymbols -> Expression -> PB.Expression
 expressionToPb s e =
   let ops = opToPb s <$> toStack e
-   in PB.ExpressionV2 { ops = PB.putField ops }
+   in PB.Expression { ops = PB.putField ops }
 
 pbToOp :: Symbols -> PB.Op -> Either String Op
 pbToOp s = \case
   PB.OpVValue v  -> VOp <$> pbToTerm s (PB.getField v)
-  PB.OpVUnary v  -> pure . UOp . pbToUnary $ PB.getField v
-  PB.OpVBinary v -> pure . BOp . pbToBinary $ PB.getField v
+  PB.OpVUnary v  -> UOp <$> pbToUnary s (PB.getField v)
+  PB.OpVBinary v -> BOp <$> pbToBinary s (PB.getField v)
+  PB.OpVClosure v -> uncurry COp <$> pbToClosure s (PB.getField v)
 
 opToPb :: ReverseSymbols -> Op -> PB.Op
 opToPb s = \case
   VOp t -> PB.OpVValue  $ PB.putField $ termToPb s t
-  UOp o -> PB.OpVUnary  $ PB.putField $ unaryToPb o
-  BOp o -> PB.OpVBinary $ PB.putField $ binaryToPb o
+  UOp o -> PB.OpVUnary  $ PB.putField $ unaryToPb s o
+  BOp o -> PB.OpVBinary $ PB.putField $ binaryToPb s o
+  COp p os -> PB.OpVClosure $ PB.putField $ closureToPb s p os
 
-pbToUnary :: PB.OpUnary -> Unary
-pbToUnary PB.OpUnary{kind} = case PB.getField kind of
-  PB.Negate -> Negate
-  PB.Parens -> Parens
-  PB.Length -> Length
+pbToUnary :: Symbols -> PB.OpUnary -> Either String Unary
+pbToUnary s PB.OpUnary{kind,ffiName} =
+  let noFfi = case PB.getField ffiName of
+                Just _  -> const $ Left "FFI named set on a regular operation "
+                Nothing -> Right
+   in case PB.getField kind of
+        PB.Negate -> noFfi Negate
+        PB.Parens -> noFfi Parens
+        PB.Length -> noFfi Length
+        PB.TypeOf -> noFfi TypeOf
+        PB.UnaryFfi -> do
+          nameIdx <- maybeToRight "Missing extern call name" $ PB.getField ffiName
+          name' <- getSymbol s $ SymbolRef nameIdx
+          pure $ UnaryFfi name'
 
-unaryToPb ::  Unary -> PB.OpUnary
-unaryToPb = PB.OpUnary . PB.putField . \case
-  Negate -> PB.Negate
-  Parens -> PB.Parens
-  Length -> PB.Length
+unaryToPb :: ReverseSymbols -> Unary -> PB.OpUnary
+unaryToPb s = \case
+  Negate -> PB.OpUnary { kind = PB.putField PB.Negate, ffiName = PB.putField Nothing }
+  Parens -> PB.OpUnary { kind = PB.putField PB.Parens, ffiName = PB.putField Nothing }
+  Length -> PB.OpUnary { kind = PB.putField PB.Length, ffiName = PB.putField Nothing }
+  TypeOf -> PB.OpUnary { kind = PB.putField PB.TypeOf, ffiName = PB.putField Nothing }
+  UnaryFfi name ->
+    PB.OpUnary {
+      kind = PB.putField PB.UnaryFfi,
+      ffiName = PB.putField . Just . getSymbolRef $ getSymbolCode s name
+    }
 
-pbToBinary :: PB.OpBinary -> Binary
-pbToBinary PB.OpBinary{kind} = case PB.getField kind of
-  PB.LessThan       -> LessThan
-  PB.GreaterThan    -> GreaterThan
-  PB.LessOrEqual    -> LessOrEqual
-  PB.GreaterOrEqual -> GreaterOrEqual
-  PB.Equal          -> Equal
-  PB.Contains       -> Contains
-  PB.Prefix         -> Prefix
-  PB.Suffix         -> Suffix
-  PB.Regex          -> Regex
-  PB.Add            -> Add
-  PB.Sub            -> Sub
-  PB.Mul            -> Mul
-  PB.Div            -> Div
-  PB.And            -> And
-  PB.Or             -> Or
-  PB.Intersection   -> Intersection
-  PB.Union          -> Union
-  PB.BitwiseAnd     -> BitwiseAnd
-  PB.BitwiseOr      -> BitwiseOr
-  PB.BitwiseXor     -> BitwiseXor
-  PB.NotEqual       -> NotEqual
+pbToBinary :: Symbols -> PB.OpBinary -> Either String Binary
+pbToBinary s PB.OpBinary{kind, ffiName} =
+  let noFfi = case PB.getField ffiName of
+                Just _  -> const $ Left "FFI named set on a regular operation "
+                Nothing -> Right
+   in case PB.getField kind of
+        PB.LessThan              -> noFfi LessThan
+        PB.GreaterThan           -> noFfi GreaterThan
+        PB.LessOrEqual           -> noFfi LessOrEqual
+        PB.GreaterOrEqual        -> noFfi GreaterOrEqual
+        PB.Equal                 -> noFfi Equal
+        PB.Contains              -> noFfi Contains
+        PB.Prefix                -> noFfi Prefix
+        PB.Suffix                -> noFfi Suffix
+        PB.Regex                 -> noFfi Regex
+        PB.Add                   -> noFfi Add
+        PB.Sub                   -> noFfi Sub
+        PB.Mul                   -> noFfi Mul
+        PB.Div                   -> noFfi Div
+        PB.And                   -> noFfi And
+        PB.Or                    -> noFfi Or
+        PB.Intersection          -> noFfi Intersection
+        PB.Union                 -> noFfi Union
+        PB.BitwiseAnd            -> noFfi BitwiseAnd
+        PB.BitwiseOr             -> noFfi BitwiseOr
+        PB.BitwiseXor            -> noFfi BitwiseXor
+        PB.NotEqual              -> noFfi NotEqual
+        PB.HeterogeneousEqual    -> noFfi HeterogeneousEqual
+        PB.HeterogeneousNotEqual -> noFfi HeterogeneousNotEqual
+        PB.LazyAnd               -> noFfi LazyAnd
+        PB.LazyOr                -> noFfi LazyOr
+        PB.All                   -> noFfi All
+        PB.Any                   -> noFfi Any
+        PB.Get                   -> noFfi Get
+        PB.TryOr                 -> noFfi Try
+        PB.BinaryFfi -> do
+          nameIdx <- maybeToRight "Missing extern call name" $ PB.getField ffiName
+          name' <- getSymbol s $ SymbolRef nameIdx
+          pure $ BinaryFfi name'
 
-binaryToPb :: Binary -> PB.OpBinary
-binaryToPb = PB.OpBinary . PB.putField . \case
-  LessThan       -> PB.LessThan
-  GreaterThan    -> PB.GreaterThan
-  LessOrEqual    -> PB.LessOrEqual
-  GreaterOrEqual -> PB.GreaterOrEqual
-  Equal          -> PB.Equal
-  Contains       -> PB.Contains
-  Prefix         -> PB.Prefix
-  Suffix         -> PB.Suffix
-  Regex          -> PB.Regex
-  Add            -> PB.Add
-  Sub            -> PB.Sub
-  Mul            -> PB.Mul
-  Div            -> PB.Div
-  And            -> PB.And
-  Or             -> PB.Or
-  Intersection   -> PB.Intersection
-  Union          -> PB.Union
-  BitwiseAnd     -> PB.BitwiseAnd
-  BitwiseOr      -> PB.BitwiseOr
-  BitwiseXor     -> PB.BitwiseXor
-  NotEqual       -> PB.NotEqual
+pbToClosure :: Symbols -> PB.OpClosure -> Either String ([T.Text], [Op])
+pbToClosure s PB.OpClosure{..} =
+  let getParams = traverse (getSymbol s . SymbolRef) . PB.getField
+      getOps = traverse (pbToOp s) . PB.getField
+   in bitraverse getParams getOps (params, ops)
 
-pbToThirdPartyBlockRequest :: PB.ThirdPartyBlockRequest -> Either String Crypto.PublicKey
-pbToThirdPartyBlockRequest PB.ThirdPartyBlockRequest{previousPk, pkTable} = do
+closureToPb :: ReverseSymbols -> [T.Text] -> [Op] -> PB.OpClosure
+closureToPb s params' ops' =
+  let params = PB.putField $ fmap (getSymbolRef . getSymbolCode s) params'
+      ops = PB.putField $ fmap (opToPb s) ops'
+   in PB.OpClosure{..}
+
+binaryToPb :: ReverseSymbols -> Binary -> PB.OpBinary
+binaryToPb s = \case
+  LessThan       -> PB.OpBinary { kind = PB.putField PB.LessThan, ffiName = PB.putField Nothing }
+  GreaterThan    -> PB.OpBinary { kind = PB.putField PB.GreaterThan, ffiName = PB.putField Nothing }
+  LessOrEqual    -> PB.OpBinary { kind = PB.putField PB.LessOrEqual, ffiName = PB.putField Nothing }
+  GreaterOrEqual -> PB.OpBinary { kind = PB.putField PB.GreaterOrEqual, ffiName = PB.putField Nothing }
+  Equal          -> PB.OpBinary { kind = PB.putField PB.Equal, ffiName = PB.putField Nothing }
+  Contains       -> PB.OpBinary { kind = PB.putField PB.Contains, ffiName = PB.putField Nothing }
+  Prefix         -> PB.OpBinary { kind = PB.putField PB.Prefix, ffiName = PB.putField Nothing }
+  Suffix         -> PB.OpBinary { kind = PB.putField PB.Suffix, ffiName = PB.putField Nothing }
+  Regex          -> PB.OpBinary { kind = PB.putField PB.Regex, ffiName = PB.putField Nothing }
+  Add            -> PB.OpBinary { kind = PB.putField PB.Add, ffiName = PB.putField Nothing }
+  Sub            -> PB.OpBinary { kind = PB.putField PB.Sub, ffiName = PB.putField Nothing }
+  Mul            -> PB.OpBinary { kind = PB.putField PB.Mul, ffiName = PB.putField Nothing }
+  Div            -> PB.OpBinary { kind = PB.putField PB.Div, ffiName = PB.putField Nothing }
+  And            -> PB.OpBinary { kind = PB.putField PB.And, ffiName = PB.putField Nothing }
+  Or             -> PB.OpBinary { kind = PB.putField PB.Or, ffiName = PB.putField Nothing }
+  Intersection   -> PB.OpBinary { kind = PB.putField PB.Intersection, ffiName = PB.putField Nothing }
+  Union          -> PB.OpBinary { kind = PB.putField PB.Union, ffiName = PB.putField Nothing }
+  BitwiseAnd     -> PB.OpBinary { kind = PB.putField PB.BitwiseAnd, ffiName = PB.putField Nothing }
+  BitwiseOr      -> PB.OpBinary { kind = PB.putField PB.BitwiseOr, ffiName = PB.putField Nothing }
+  BitwiseXor     -> PB.OpBinary { kind = PB.putField PB.BitwiseXor, ffiName = PB.putField Nothing }
+  NotEqual       -> PB.OpBinary { kind = PB.putField PB.NotEqual, ffiName = PB.putField Nothing }
+  HeterogeneousEqual -> PB.OpBinary { kind = PB.putField PB.HeterogeneousEqual, ffiName = PB.putField Nothing }
+  HeterogeneousNotEqual -> PB.OpBinary { kind = PB.putField PB.HeterogeneousNotEqual, ffiName = PB.putField Nothing }
+  LazyAnd ->PB.OpBinary { kind = PB.putField PB.LazyAnd, ffiName = PB.putField Nothing }
+  LazyOr -> PB.OpBinary { kind = PB.putField PB.LazyOr, ffiName = PB.putField Nothing }
+  Any -> PB.OpBinary { kind = PB.putField PB.Any, ffiName = PB.putField Nothing }
+  All -> PB.OpBinary { kind = PB.putField PB.All, ffiName = PB.putField Nothing }
+  Get -> PB.OpBinary { kind = PB.putField PB.Get, ffiName = PB.putField Nothing }
+  Try -> PB.OpBinary { kind = PB.putField PB.TryOr, ffiName = PB.putField Nothing }
+  BinaryFfi n -> PB.OpBinary
+    { kind = PB.putField PB.BinaryFfi
+    , ffiName = PB.putField . Just . getSymbolRef $ getSymbolCode s n
+    }
+
+pbToThirdPartyBlockRequest :: PB.ThirdPartyBlockRequest -> Either String Crypto.Signature
+pbToThirdPartyBlockRequest PB.ThirdPartyBlockRequest{legacyPk, pkTable, prevSig} = do
+  unless (isNothing $ PB.getField legacyPk) $ Left "Public key provided in third-party block request"
   unless (null $ PB.getField pkTable) $ Left "Public key table provided in third-party block request"
-  pbToPublicKey $ PB.getField previousPk
+  pure . Crypto.signature $ PB.getField prevSig
 
-thirdPartyBlockRequestToPb :: Crypto.PublicKey -> PB.ThirdPartyBlockRequest
-thirdPartyBlockRequestToPb previousPk = PB.ThirdPartyBlockRequest
-  { previousPk = PB.putField $ publicKeyToPb previousPk
+thirdPartyBlockRequestToPb :: Crypto.Signature -> PB.ThirdPartyBlockRequest
+thirdPartyBlockRequestToPb prevSig = PB.ThirdPartyBlockRequest
+  { legacyPk = PB.putField Nothing
   , pkTable = PB.putField []
+  , prevSig = PB.putField $ Crypto.sigBytes prevSig
   }
 
 pbToThirdPartyBlockContents :: PB.ThirdPartyBlockContents -> Either String (ByteString, Crypto.Signature, Crypto.PublicKey)
diff --git a/src/Auth/Biscuit/Symbols.hs b/src/Auth/Biscuit/Symbols.hs
--- a/src/Auth/Biscuit/Symbols.hs
+++ b/src/Auth/Biscuit/Symbols.hs
@@ -3,6 +3,13 @@
 {-# LANGUAGE NamedFieldPuns             #-}
 {-# LANGUAGE OverloadedLists            #-}
 {-# LANGUAGE OverloadedStrings          #-}
+{-|
+  Module      : Auth.Biscuit.Symbols
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+  Maintainer  : clement@delafargue.name
+  Symbol management logic for biscuit serialization
+-}
 module Auth.Biscuit.Symbols
   ( Symbols
   , BlockSymbols
diff --git a/src/Auth/Biscuit/Timer.hs b/src/Auth/Biscuit/Timer.hs
--- a/src/Auth/Biscuit/Timer.hs
+++ b/src/Auth/Biscuit/Timer.hs
@@ -1,7 +1,7 @@
 {-|
   Module      : Auth.Biscuit.Timer
   Copyright   : © Clément Delafargue, 2021
-  License     : MIT
+  License     : BSD-3-Clause
   Maintainer  : clement@delafargue.name
   Helper function making sure an IO action runs in an alloted time
 -}
diff --git a/src/Auth/Biscuit/Token.hs b/src/Auth/Biscuit/Token.hs
--- a/src/Auth/Biscuit/Token.hs
+++ b/src/Auth/Biscuit/Token.hs
@@ -8,7 +8,7 @@
 {-|
   Module      : Auth.Biscuit.Token
   Copyright   : © Clément Delafargue, 2021
-  License     : MIT
+  License     : BSD-3-Clause
   Maintainer  : clement@delafargue.name
   Module defining the main biscuit-related operations
 -}
@@ -75,12 +75,13 @@
                                                       Signature, SignedBlock,
                                                       getSignatureProof,
                                                       sigBytes,
-                                                      sign3rdPartyBlock,
-                                                      signBlock,
+                                                      sign3rdPartyBlockV1,
+                                                      signAttenuationBlock,
+                                                      signAuthority,
                                                       signExternalBlock,
                                                       skBytes, toPublic,
                                                       verifyBlocks,
-                                                      verifyExternalSig,
+                                                      verifyExternalSigV1,
                                                       verifySecretProof,
                                                       verifySignatureProof)
 import           Auth.Biscuit.Datalog.AST            (Authorizer, Block, Query,
@@ -107,7 +108,7 @@
 -- so we need to keep the initial serialized payload around in order to compute
 -- a new signature when adding a block.
 type ExistingBlock = (ByteString, Block)
-type ParsedSignedBlock = (ExistingBlock, Signature, PublicKey, Maybe (Signature, PublicKey))
+type ParsedSignedBlock = (ExistingBlock, Signature, PublicKey, Maybe (Signature, PublicKey), Maybe Int)
 
 -- $openOrSealed
 --
@@ -218,7 +219,7 @@
                                -> Either String (Set Bindings)
 queryRawBiscuitFactsWithLimits b@Biscuit{authority,blocks} =
   let ePks = externalKeys b
-      getBlock ((_, block), _, _, _) = block
+      getBlock ((_, block), _, _, _, _) = block
       allBlocks = zip [0..] $ getBlock <$> authority : blocks
       (_, sFacts) = foldMap (uncurry collectWorld . fmap (toEvaluation ePks)) allBlocks
    in queryAvailableFacts ePks sFacts
@@ -263,7 +264,7 @@
   _           -> Nothing
 
 toParsedSignedBlock :: Block -> SignedBlock -> ParsedSignedBlock
-toParsedSignedBlock block (serializedBlock, sig, pk, eSig) = ((serializedBlock, block), sig, pk, eSig)
+toParsedSignedBlock block (serializedBlock, sig, pk, eSig, sigVersion) = ((serializedBlock, block), sig, pk, eSig, sigVersion)
 
 -- | Create a new biscuit with the provided authority block. Such a biscuit is 'Open' to
 -- further attenuation.
@@ -274,8 +275,8 @@
 -- further attenuation.
 mkBiscuitWith :: Maybe Int -> SecretKey -> Block -> IO (Biscuit Open Verified)
 mkBiscuitWith rootKeyId sk authority = do
-  let (authoritySymbols, authoritySerialized) = PB.encodeBlock <$> blockToPb False newSymbolTable authority
-  (signedBlock, nextSk) <- signBlock sk authoritySerialized Nothing
+  let ((authoritySymbols, authorityVersion), authoritySerialized) = PB.encodeBlock <$> blockToPb False newSymbolTable authority
+  (signedBlock, nextSk) <- signAuthority sk (authoritySerialized, authorityVersion)
   pure Biscuit { rootKeyId
                , authority = toParsedSignedBlock authority signedBlock
                , blocks = []
@@ -290,9 +291,10 @@
          -> Biscuit Open check
          -> IO (Biscuit Open check)
 addBlock block b@Biscuit{..} = do
-  let (blockSymbols, blockSerialized) = PB.encodeBlock <$> blockToPb False symbols block
-      Open p = proof
-  (signedBlock, nextSk) <- signBlock p blockSerialized Nothing
+  let ((blockSymbols, version), blockSerialized) = PB.encodeBlock <$> blockToPb False symbols block
+      Open sk = proof
+      (_, prevSig, _, _,_) = NE.last $ authority :| blocks
+  (signedBlock, nextSk) <- signAttenuationBlock sk prevSig (blockSerialized, version) Nothing
   pure $ b { blocks = blocks <> [toParsedSignedBlock block signedBlock]
            , symbols = addFromBlock symbols blockSymbols
            , proof = Open nextSk
@@ -306,22 +308,22 @@
                -> Biscuit Open check
                -> IO (Biscuit Open check)
 addSignedBlock eSk block b@Biscuit{..} = do
-  let (_, blockSerialized) = PB.encodeBlock <$> blockToPb True newSymbolTable block
+  let ((_, version), blockSerialized) = PB.encodeBlock <$> blockToPb True newSymbolTable block
       lastBlock = NE.last (authority :| blocks)
-      (_, _, lastPublicKey, _) = lastBlock
-      Open p = proof
-  (signedBlock, nextSk) <- signExternalBlock p eSk lastPublicKey blockSerialized
+      (_, prevSig, _, _, _) = lastBlock
+      Open sk = proof
+  (signedBlock, nextSk) <- signExternalBlock sk prevSig (blockSerialized, version) eSk
   pure $ b { blocks = blocks <> [toParsedSignedBlock block signedBlock]
            , proof = Open nextSk
            }
 
 mkThirdPartyBlock' :: SecretKey
-                   -> PublicKey
+                   -> Signature
                    -> Block
                    -> (ByteString, Signature, PublicKey)
-mkThirdPartyBlock' eSk lastPublicKey block =
+mkThirdPartyBlock' eSk prevSig block =
   let (_, payload) = PB.encodeBlock <$> blockToPb True newSymbolTable block
-      (eSig, ePk) = sign3rdPartyBlock eSk lastPublicKey payload
+      (eSig, ePk) = sign3rdPartyBlockV1 eSk prevSig payload
    in (payload, eSig, ePk)
 
 -- | Given a third-party block request, generate a third-party block,
@@ -331,8 +333,8 @@
                   -> Block
                   -> Either String ByteString
 mkThirdPartyBlock eSk req block = do
-  previousPk<- pbToThirdPartyBlockRequest =<< PB.decodeThirdPartyBlockRequest req
-  pure $ PB.encodeThirdPartyBlockContents . thirdPartyBlockContentsToPb $ mkThirdPartyBlock' eSk previousPk block
+  prevSig <- pbToThirdPartyBlockRequest =<< PB.decodeThirdPartyBlockRequest req
+  pure $ PB.encodeThirdPartyBlockContents . thirdPartyBlockContentsToPb $ mkThirdPartyBlock' eSk prevSig block
 
 -- | Generate a third-party block request. It can be used in
 -- conjunction with 'mkThirdPartyBlock' to generate a
@@ -340,22 +342,22 @@
 -- 'applyThirdPartyBlock'.
 mkThirdPartyBlockReq :: Biscuit proof check -> ByteString
 mkThirdPartyBlockReq Biscuit{authority,blocks} =
-  let (_, _ , lastPk, _) = NE.last $ authority :| blocks
-   in PB.encodeThirdPartyBlockRequest $ thirdPartyBlockRequestToPb lastPk
+  let (_, prevSig , _, _, _) = NE.last $ authority :| blocks
+   in PB.encodeThirdPartyBlockRequest $ thirdPartyBlockRequestToPb prevSig
 
 -- | Given a base64-encoded third-party block, append it to a token.
 applyThirdPartyBlock :: Biscuit Open check -> ByteString -> Either String (IO (Biscuit Open check))
 applyThirdPartyBlock b@Biscuit{..} contents = do
   (payload, eSig, ePk) <- pbToThirdPartyBlockContents =<< PB.decodeThirdPartyBlockContents contents
-  let Open p = proof
-      addESig (a,b',c,_) = (a,b',c, Just (eSig, ePk))
-      (_, _, lastPk, _) = NE.last $ authority :| blocks
+  let Open sk = proof
+      addESig (a,b',c,_, d) = (a,b',c, Just (eSig, ePk), d)
+      (_, prevSig, _, _, _) = NE.last $ authority :| blocks
   pbBlock <- PB.decodeBlock payload
   (block, newSymbols) <- (`runStateT` symbols) $ pbToBlock (Just ePk) pbBlock
-  unless (verifyExternalSig lastPk (payload, eSig, ePk)) $
+  unless (verifyExternalSigV1 prevSig (payload, eSig, ePk)) $
     Left "Invalid 3rd party signature"
   pure $ do
-    (signedBlock, nextSk) <- signBlock p payload (Just (eSig, ePk))
+    (signedBlock, nextSk) <- signAttenuationBlock sk prevSig (payload, 3) (Just (eSig, ePk))
     pure $ b { blocks = blocks <> [toParsedSignedBlock block (addESig signedBlock)]
              , proof = Open nextSk
              , symbols = newSymbols
@@ -363,8 +365,8 @@
 
 externalKeys :: Biscuit openOrSealed check -> [Maybe PublicKey]
 externalKeys Biscuit{blocks} =
-  let getEpk (_, _, _, Just (_, ePk)) = Just ePk
-      getEpk _                        = Nothing
+  let getEpk (_, _, _, Just (_, ePk), _) = Just ePk
+      getEpk _                           = Nothing
    in Nothing : (getEpk <$> blocks)
 
 -- | Turn an 'Open' biscuit into a 'Sealed' one, preventing it from being attenuated
@@ -372,8 +374,8 @@
 seal :: Biscuit Open check -> Biscuit Sealed check
 seal b@Biscuit{..} =
   let Open sk = proof
-      ((lastPayload, _), lastSig, lastPk, eSig) = NE.last $ authority :| blocks
-      newProof = Sealed $ getSignatureProof (lastPayload, lastSig, lastPk, eSig) sk
+      ((lastPayload, _), lastSig, lastPk, eSig, _) = NE.last $ authority :| blocks
+      newProof = Sealed $ getSignatureProof (lastPayload, lastSig, lastPk, eSig, Nothing) sk
    in b { proof = newProof }
 
 -- | Serialize a biscuit to a raw bytestring
@@ -390,7 +392,7 @@
         }
 
 toPBSignedBlock :: ParsedSignedBlock -> PB.SignedBlock
-toPBSignedBlock ((block, _), sig, pk, eSig) = signedBlockToPb (block, sig, pk, eSig)
+toPBSignedBlock ((block, _), sig, pk, eSig, sigVersion) = signedBlockToPb (block, sig, pk, eSig, sigVersion)
 
 -- | Errors that can happen when parsing a biscuit. Since complete parsing of a biscuit
 -- requires a signature check, an invalid signature check is a parsing error
@@ -444,7 +446,7 @@
                 -> BiscuitWrapper
                 -> m (Either ParseError BiscuitWrapper)
 checkRevocation isRevoked bw@BiscuitWrapper{wAuthority,wBlocks} =
-  let getRevocationId (_, sig, _, _) = sigBytes sig
+  let getRevocationId (_, sig, _, _, _) = sigBytes sig
       revocationIds = getRevocationId <$> wAuthority :| wBlocks
       keepIfNotRevoked True  = Left RevokedBiscuit
       keepIfNotRevoked False = Right bw
@@ -452,10 +454,10 @@
 
 parseBlocks :: BiscuitWrapper -> Either ParseError (Symbols, NonEmpty ParsedSignedBlock)
 parseBlocks BiscuitWrapper{..} = do
-  let parseBlock (payload, sig, pk, eSig) = do
+  let parseBlock (payload, sig, pk, eSig, sigVersion) = do
         pbBlock <- lift $ first (InvalidProtobufSer False) $ PB.decodeBlock payload
         block   <- mapStateT (first (InvalidProtobuf False)) $ pbToBlock (snd <$> eSig) pbBlock
-        pure ((payload, block), sig, pk, eSig)
+        pure ((payload, block), sig, pk, eSig,sigVersion)
 
   (allBlocks, symbols) <- (`runStateT` newSymbolTable) $ do
      traverse parseBlock (wAuthority :| wBlocks)
@@ -502,7 +504,7 @@
                        -> Either ParseError (Biscuit proof Verified)
 checkBiscuitSignatures getPublicKey b@Biscuit{..} = do
   let pk = getPublicKey rootKeyId
-      toSignedBlock ((payload, _), sig, nextPk, eSig) = (payload, sig, nextPk, eSig)
+      toSignedBlock ((payload, _), sig, nextPk, eSig, sigVersion) = (payload, sig, nextPk, eSig, sigVersion)
       allBlocks = toSignedBlock <$> (authority :| blocks)
       blocksResult = verifyBlocks allBlocks pk
       proofResult = case toPossibleProofs proof of
@@ -536,7 +538,7 @@
 parseBiscuitWith ParserConfig{..} bs =
   let input = case encoding of
         RawBytes  -> Right bs
-        UrlBase64 -> first (const InvalidB64Encoding) . B64.decodeBase64 $ bs
+        UrlBase64 -> first (const InvalidB64Encoding) . B64.decodeBase64Untyped $ bs
       parsedWrapper = parseBiscuitWrapper =<< input
       wrapperToBiscuit w@BiscuitWrapper{wRootKeyId} =
         let pk = getPublicKey wRootKeyId
@@ -550,13 +552,13 @@
 getRevocationIds :: Biscuit proof check -> NonEmpty ByteString
 getRevocationIds Biscuit{authority, blocks} =
   let allBlocks = authority :| blocks
-      getRevocationId (_, sig, _, _) = sigBytes sig
+      getRevocationId (_, sig, _, _, _) = sigBytes sig
    in getRevocationId <$> allBlocks
 
 -- | Generic version of 'authorizeBiscuitWithLimits' which takes custom 'Limits'.
 authorizeBiscuitWithLimits :: Limits -> Biscuit proof Verified -> Authorizer -> IO (Either ExecutionError (AuthorizedBiscuit proof))
 authorizeBiscuitWithLimits l biscuit@Biscuit{..} authorizer =
-  let toBlockWithRevocationId ((_, block), sig, _, eSig) = (block, sigBytes sig, snd <$> eSig)
+  let toBlockWithRevocationId ((_, block), sig, _, eSig, _) = (block, sigBytes sig, snd <$> eSig)
       -- the authority block can't be externally signed. If it carries a signature, it won't be
       -- verified. So we need to make sure there is none, to avoid having facts trusted without
       -- a proper signature check
diff --git a/src/Auth/Biscuit/Utils.hs b/src/Auth/Biscuit/Utils.hs
--- a/src/Auth/Biscuit/Utils.hs
+++ b/src/Auth/Biscuit/Utils.hs
@@ -3,7 +3,7 @@
 -- |
 --  Module      : Auth.Biscuit.Utils
 --  Copyright   : © Clément Delafargue, 2021
---  License     : MIT
+--  License     : BSD-3-Clause
 --  Maintainer  : clement@delafargue.name
 module Auth.Biscuit.Utils
   ( maybeToRight,
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,7 +1,12 @@
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Main (main) where
 
 import           Test.Tasty
 
+import qualified Spec.AST            as AST
 import qualified Spec.Executor       as Executor
 import qualified Spec.NewCrypto      as NewCrypto
 import qualified Spec.Parser         as Parser
@@ -15,8 +20,8 @@
 main = do
   sampleReader <- SampleReader.getSpecs
   defaultMain $ testGroup "biscuit-haskell"
-    [
-      NewCrypto.specs
+    [ AST.specs
+    , NewCrypto.specs
     , Executor.specs
     , Parser.specs
     , Quasiquoter.specs
diff --git a/test/Spec/AST.hs b/test/Spec/AST.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec/AST.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Spec.AST (specs) where
+
+import           Test.Tasty
+import           Test.Tasty.HUnit
+
+import           Auth.Biscuit.Datalog.AST
+
+specs :: TestTree
+specs = testGroup "datalog AST"
+  [ toStackClosure
+  , fromStackClosure
+  ]
+
+toStackClosure :: TestTree
+toStackClosure = testCase "Turn a closure expr into a stack" $
+  let cE = EClosure
+             ["x"]
+             (EBinary
+               Equal
+               (EValue (Variable "x"))
+               (EValue (LInteger 42)))
+      cO = [COp ["x"]
+            [ VOp (Variable "x")
+            , VOp (LInteger 42)
+            , BOp Equal
+            ]]
+   in toStack cE @?= cO
+
+
+fromStackClosure :: TestTree
+fromStackClosure = testCase "Turn a closure op into an expression" $
+  let cO = [COp ["x"]
+            [ VOp (Variable "x")
+            , VOp (LInteger 42)
+            , BOp Equal
+            ]]
+      cE = EClosure
+            ["x"]
+            (EBinary
+              Equal
+              (EValue (Variable "x"))
+              (EValue (LInteger 42)))
+   in fromStack cO @?= Right cE
diff --git a/test/Spec/Executor.hs b/test/Spec/Executor.hs
--- a/test/Spec/Executor.hs
+++ b/test/Spec/Executor.hs
@@ -1,6 +1,10 @@
 {-# LANGUAGE OverloadedLists   #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes       #-}
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Spec.Executor (specs) where
 
 import           Data.Map.Strict                     as Map
@@ -96,7 +100,7 @@
    --   ("1 / 0") @?= Left "Divide by 0"
   testGroup "Expressions evaluation" $ eval <$>
     [ ("!(1 < $var1)", LBool True)
-    , ("[0].contains($var1)", LBool True)
+    , ("{0}.contains($var1)", LBool True)
     , ("1 + 2 * 3", LInteger 7)
     , ("!(1 + 2 * 3 > 4)", LBool False)
     , ("!true", LBool False)
@@ -105,22 +109,22 @@
     , ("\"test\".length()", LInteger 4)
     , ("\"é\".length()", LInteger 2)
     , ("hex:ababab.length()", LInteger 3)
-    , ("[].length()", LInteger 0)
-    , ("[\"test\", \"test\"].length()", LInteger 1)
-    , ("1 == 1", LBool True)
-    , ("2 == 1", LBool False)
-    , ("\"toto\" == \"toto\"", LBool True)
-    , ("\"toto\" == \"truc\"", LBool False)
+    , ("{,}.length()", LInteger 0)
+    , ("{\"test\", \"test\"}.length()", LInteger 1)
+    , ("1 === 1", LBool True)
+    , ("2 === 1", LBool False)
+    , ("\"toto\" === \"toto\"", LBool True)
+    , ("\"toto\" === \"truc\"", LBool False)
     , ("\"toto\".matches(\"to(to)?\")", LBool True)
     , ("\"toto\".matches(\"^to$\")", LBool False)
-    , ("2021-05-07T18:00:00Z == 2021-05-07T18:00:00Z", LBool True)
-    , ("2021-05-07T18:00:00Z == 2021-05-07T19:00:00Z", LBool False)
-    , ("hex:ababab == hex:ababab", LBool True)
-    , ("hex:ababab == hex:ababac", LBool False)
-    , ("true == true", LBool True)
-    , ("true == false", LBool False)
-    , ("[1,2,3] == [1,2,3]", LBool True)
-    , ("[1,2,3] == [1,2,4]", LBool False)
+    , ("2021-05-07T18:00:00Z === 2021-05-07T18:00:00Z", LBool True)
+    , ("2021-05-07T18:00:00Z === 2021-05-07T19:00:00Z", LBool False)
+    , ("hex:ababab === hex:ababab", LBool True)
+    , ("hex:ababab === hex:ababac", LBool False)
+    , ("true === true", LBool True)
+    , ("true === false", LBool False)
+    , ("{1,2,3} === {1,2,3}", LBool True)
+    , ("{1,2,3} === {1,2,4}", LBool False)
     , ("1 < 2", LBool True)
     , ("2 < 1", LBool False)
     , ("2021-05-07T18:00:00Z < 2021-05-07T19:00:00Z", LBool True)
@@ -158,14 +162,15 @@
     , ("true || false", LBool True)
     , ("false || true", LBool True)
     , ("false || false", LBool False)
-    , ("[1].contains([1])", LBool True)
-    , ("[1].contains(1)", LBool True)
-    , ("[].contains(1)", LBool False)
-    , ("[\"test\"].contains(2)", LBool False)
-    , ("[1].intersection([1])", TermSet (Set.fromList [LInteger 1]))
-    , ("[1].intersection([\"test\"])", TermSet (Set.fromList []))
-    , ("[1].union([1])", TermSet (Set.fromList [LInteger 1]))
-    , ("[1].union([\"test\"])", TermSet (Set.fromList [LInteger 1, LString "test"]))
+    , ("{1}.contains({1})", LBool True)
+    , ("{1}.contains(1)", LBool True)
+    , ("{,}.contains(1)", LBool False)
+    , ("{\"test\"}.contains(2)", LBool False)
+    , ("{1}.intersection({1})", TermSet (Set.fromList [LInteger 1]))
+    , ("{1}.intersection({\"test\"})", TermSet (Set.fromList []))
+    , ("{1}.union({1})", TermSet (Set.fromList [LInteger 1]))
+    , ("{1}.union({\"test\"})", TermSet (Set.fromList [LInteger 1, LString "test"]))
+    , ("(true === 12).try_or(42)", LInteger 42)
     ]
 
 exprEvalError :: TestTree
@@ -182,6 +187,7 @@
     , ("\"toto\".matches(\"to\")", "Regex evaluation is disabled")
     , ("9223372036854775807 + 1", "integer overflow")
     , ("-9223372036854775808 - 1", "integer underflow")
+    , ("true.try_or(true === 12)", "Equality mismatch") -- the right-hand-side of try_or is eager
     ]
 
 rulesWithConstraints :: TestTree
@@ -302,11 +308,11 @@
 overflow :: TestTree
 overflow =
   let subtraction = authRulesGroup $ Set.singleton
-                   [rule|test(true) <- -9223372036854775808 - 1 != 0|]
+                   [rule|test(true) <- -9223372036854775808 - 1 !== 0|]
       multiplication = authRulesGroup $ Set.singleton
-                   [rule|test(true) <- 10000000000 * 10000000000 != 0|]
+                   [rule|test(true) <- 10000000000 * 10000000000 !== 0|]
       addition = authRulesGroup $ Set.singleton
-                   [rule|test(true) <- 9223372036854775807 + 1 != 0|]
+                   [rule|test(true) <- 9223372036854775807 + 1 !== 0|]
    in testGroup "Arithmetic overflow"
         [ testCase "subtraction" $
             runFactGeneration defaultLimits 1 subtraction mempty @?= Left (BadExpression "integer underflow")
diff --git a/test/Spec/NewCrypto.hs b/test/Spec/NewCrypto.hs
--- a/test/Spec/NewCrypto.hs
+++ b/test/Spec/NewCrypto.hs
@@ -3,6 +3,10 @@
 {-# LANGUAGE OverloadedStrings     #-}
 {-# LANGUAGE RecordWildCards       #-}
 {- HLINT ignore "Reduce duplication" -}
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Spec.NewCrypto (specs) where
 
 import           Data.ByteString     (ByteString)
@@ -29,7 +33,7 @@
 
 signToken :: ByteString -> SecretKey -> IO Token
 signToken p sk = do
-  (signedBlock, privKey) <- signBlock sk p Nothing
+  (signedBlock, privKey) <- signAuthority sk (p, 3)
   pure Token
     { payload = pure signedBlock
     , privKey
@@ -40,7 +44,8 @@
 
 append :: Token -> ByteString -> IO Token
 append t@Token{payload} p = do
-  (signedBlock, privKey) <- signBlock (privKey t) p Nothing
+  let (_, lastSig, _, _, _) = NE.last payload
+  (signedBlock, privKey) <- signAttenuationBlock (privKey t) lastSig (p, 3) Nothing
   pure Token
     { payload = snocNE payload signedBlock
     , privKey
@@ -48,8 +53,8 @@
 
 appendSigned :: Token -> SecretKey -> ByteString -> IO Token
 appendSigned t@Token{payload} eSk p = do
-  let (_, _, lastPk, _) = NE.last payload
-  (signedBlock, privKey) <- signExternalBlock (privKey t) eSk lastPk p
+  let (_, lastSig, lastPk, _, _) = NE.last payload
+  (signedBlock, privKey) <- signExternalBlock (privKey t) lastSig (p, 3) eSk
   pure Token
     { payload = snocNE payload signedBlock
     , privKey
@@ -144,7 +149,7 @@
   attenuated <- appendSigned token eSk "block1"
   let bogusSignature = sign eSk ePk ("yolo yolo" :: ByteString)
       replaceExternalSig :: SignedBlock -> SignedBlock
-      replaceExternalSig (p, s, pk, Just (_, ePk)) = (p, s, pk, Just (bogusSignature, ePk))
+      replaceExternalSig (p, s, pk, Just (_, ePk), v) = (p, s, pk, Just (bogusSignature, ePk), v)
       replaceExternalSig sb = sb
       tamper :: Blocks -> Blocks
       tamper = fmap replaceExternalSig
@@ -164,7 +169,7 @@
       content = "content"
   token <- signToken content sk
   attenuated <- append token "block1"
-  let tamper ((_, s, pk, eS) :| o) = ("tampered", s, pk, eS) :| o
+  let tamper ((_, s, pk, eS, v) :| o) = ("tampered", s, pk, eS, v) :| o
       tampered = alterPayload tamper attenuated
   let res = verifyToken tampered pk
   res @?= False
@@ -176,7 +181,7 @@
       content = "content"
   token <- signToken content sk
   attenuated <- append token "block1"
-  let tamper (h :| ((_, s, pk, eS): t)) = h :| (("tampered", s, pk, eS) : t)
+  let tamper (h :| ((_, s, pk, eS, v): t)) = h :| (("tampered", s, pk, eS, v) : t)
       tampered = alterPayload tamper attenuated
   let res = verifyToken tampered pk
   res @?= False
@@ -224,7 +229,7 @@
       content = "content"
   token <- signToken content sk
   attenuated <- seal <$> append token "block1"
-  let tamper ((_, s, pk, eS) :| o) = ("tampered", s, pk, eS) :| o
+  let tamper ((_, s, pk, eS, v) :| o) = ("tampered", s, pk, eS, v) :| o
       tampered = alterPayloadSealed tamper attenuated
   let res = verifySealedToken tampered pk
   res @?= False
@@ -236,7 +241,7 @@
       content = "content"
   token <- signToken content sk
   attenuated <- seal <$> append token "block1"
-  let tamper (h :| ((_, s, pk, eS): t)) = h :| (("tampered", s, pk, eS) : t)
+  let tamper (h :| ((_, s, pk, eS, v): t)) = h :| (("tampered", s, pk, eS, v) : t)
       tampered = alterPayloadSealed tamper attenuated
   let res = verifySealedToken tampered pk
   res @?= False
diff --git a/test/Spec/Parser.hs b/test/Spec/Parser.hs
--- a/test/Spec/Parser.hs
+++ b/test/Spec/Parser.hs
@@ -2,6 +2,10 @@
 {-# LANGUAGE OverloadedLists   #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards   #-}
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Spec.Parser (specs, parseExpression, parseBlock, parseAuthorizer) where
 
 import           Control.Monad               ((<=<))
@@ -67,6 +71,7 @@
 specs :: TestTree
 specs = testGroup "datalog parser"
   [ factWithDate
+  , factWithNull
   , simpleFact
   , oneLetterFact
   , simpleRule
@@ -109,7 +114,7 @@
      [ testCase "Variable name" $ parseTermQQ "{toto2_'}" @?= Right (Antiquote "toto2_'")
      , testCase "Leading underscore" $ parseTermQQ "{_toto}" @?= Right (Antiquote "_toto")
      , testCase "`_` is reserved" $ parseTermQQ "{_}" @?= Left "1:3:\n  |\n1 | {_}\n  |   ^\nunexpected '}'\nexpecting letter\n"
-     , testCase "Variables are lower-cased" $ parseTermQQ "{Toto}" @?= Left "1:2:\n  |\n1 | {Toto}\n  |  ^\nunexpected 'T'\nexpecting '_' or lowercase letter\n"
+     , testCase "Variables are lower-cased" $ parseTermQQ "{Toto}" @?= Left "1:2:\n  |\n1 | {Toto}\n  |  ^^^^^\nunexpected \"Toto}\"\nexpecting '_', lowercase letter, map (eg. {\"key\": 1}), or set (eg. {1,2,3})\n"
      , testCase "_ is lower-case" $ parseTermQQ "{_Toto}" @?= Right (Antiquote "_Toto")
      , testCase "unicode is allowed" $ parseTermQQ "{éllo}" @?= Right (Antiquote "éllo")
      ]
@@ -134,6 +139,11 @@
   parsePredicate "date(2019-12-02T13:49:53+00:00)" @?=
     Right (Predicate "date" [LDate $ read "2019-12-02 13:49:53 UTC"])
 
+factWithNull :: TestTree
+factWithNull = testCase "Parse fact containing a null value" $ do
+  parsePredicate "date(null)" @?=
+    Right (Predicate "date" [LNull])
+
 simpleRule :: TestTree
 simpleRule = testCase "Parse simple rule" $
   parseRule "right($0, \"read\") <- resource( $0), operation(\"read\")" @?=
@@ -207,17 +217,29 @@
                  (EValue (LInteger 1234))
                  )
   , testCase "int comparison (EQ)" $
-      parseExpression "$0 == 1" @?=
+      parseExpression "$0 === 1" @?=
         Right (EBinary Equal
                  (EValue (Variable "0"))
                  (EValue (LInteger 1))
                  )
   , testCase "int comparison (NEQ)" $
-      parseExpression "$0 != 1" @?=
+      parseExpression "$0 !== 1" @?=
         Right (EBinary NotEqual
                  (EValue (Variable "0"))
                  (EValue (LInteger 1))
                  )
+  , testCase "int comparison (HEQ)" $
+      parseExpression "$0 == 1" @?=
+        Right (EBinary HeterogeneousEqual
+                 (EValue (Variable "0"))
+                 (EValue (LInteger 1))
+                 )
+  , testCase "int comparison (HNEQ)" $
+      parseExpression "$0 != 1" @?=
+        Right (EBinary HeterogeneousNotEqual
+                 (EValue (Variable "0"))
+                 (EValue (LInteger 1))
+                 )
   , testCase "negative int comparison (GTE)" $
       parseExpression "$0 >= -1234" @?=
         Right (EBinary GreaterOrEqual
@@ -225,13 +247,13 @@
                  (EValue (LInteger (-1234)))
                  )
   , testCase "string comparison" $
-      parseExpression "$0 == \"abc\"" @?=
+      parseExpression "$0 === \"abc\"" @?=
         Right (EBinary Equal
                  (EValue (Variable "0"))
                  (EValue (LString "abc"))
                  )
   , testCase "string comparison (NEQ)" $
-      parseExpression "$0 != \"abc\"" @?=
+      parseExpression "$0 !== \"abc\"" @?=
         Right (EBinary NotEqual
                  (EValue (Variable "0"))
                  (EValue (LString "abc"))
@@ -255,33 +277,38 @@
                  (EValue (LString "abc"))
                  )
   , testCase "int set operation" $
-      parseExpression "[1, 2].contains($0)" @?=
+      parseExpression "{1, 2}.contains($0)" @?=
         Right (EBinary Contains
                  (EValue (TermSet $ Set.fromList [LInteger 1, LInteger 2]))
                  (EValue (Variable "0"))
                  )
   , testCase "negated int set operation" $
-      parseExpression "![1, 2].contains($0)" @?=
+      parseExpression "!{1, 2}.contains($0)" @?=
         Right (EUnary Negate
                  (EBinary Contains
                     (EValue (TermSet $ Set.fromList [LInteger 1, LInteger 2]))
                     (EValue (Variable "0"))
                     ))
   , testCase "string set operation" $
-      parseExpression "[\"abc\", \"def\"].contains($0)" @?=
+      parseExpression "{\"abc\", \"def\"}.contains($0)" @?=
         Right (EBinary Contains
                  (EValue (TermSet $ Set.fromList [LString "abc", LString "def"]))
                  (EValue (Variable "0"))
                  )
   , testCase "negated string set operation" $
-      parseExpression "![\"abc\", \"def\"].contains($0)" @?=
+      parseExpression "!{\"abc\", \"def\"}.contains($0)" @?=
         Right (EUnary Negate
                  (EBinary Contains
                     (EValue (TermSet $ Set.fromList [LString "abc", LString "def"]))
                     (EValue (Variable "0"))
                     ))
+  , testCase "empty set operation" $
+      parseExpression "{,}.length()" @?=
+        Right (EUnary Length
+                (EValue (TermSet $ Set.fromList []))
+                )
   , testCase "arithmetic operation that looks like the beginning of a RFC3339 date" $
-      parseExpression "2022-12-10==2000" @?=
+      parseExpression "2022-12-10===2000" @?=
         Right (EBinary Equal
                  (EBinary Sub
                     (EBinary Sub
@@ -293,7 +320,7 @@
                  (EValue $ LInteger 2000)
               )
   , testCase "chained method calls" $
-      parseExpression "$var.intersection([1]).union([2]).length()" @?=
+      parseExpression "$var.intersection({1}).union({2}).length()" @?=
         Right (EUnary Length
                  (EBinary Union
                     ( EBinary Intersection
@@ -303,6 +330,41 @@
                     (EValue $ TermSet [LInteger 2])
                  )
               )
+  , testCase "unary extern method call" $
+      parseExpression "$var.extern::test() == true" @?=
+        Right (EBinary HeterogeneousEqual
+                (EUnary (UnaryFfi "test") (EValue $ Variable "var"))
+                (EValue $ LBool True))
+  , testCase "binary extern method call" $
+      parseExpression "$var.extern::test(1) == true" @?=
+        Right (EBinary HeterogeneousEqual
+                (EBinary
+                  (BinaryFfi "test")
+                  (EValue $ Variable "var")
+                  (EValue $ LInteger 1))
+                  (EValue $ LBool True))
+  , testCase "nullary closures" $
+      parseExpression "true || 1 === 2" @?=
+        Right (EBinary LazyOr
+                (EValue $ LBool True)
+                  (EClosure []
+                      (EBinary Equal
+                          (EValue $ LInteger 1)
+                          (EValue $ LInteger 2)
+                      )
+                  )
+              )
+  , testCase "unary closures" $
+      parseExpression "{1}.all($p -> $p === 1)" @?=
+        Right (EBinary All
+                (EValue . TermSet . Set.singleton $ LInteger 1)
+                (EClosure ["p"]
+                    (EBinary Equal
+                      (EValue $ Variable "p")
+                      (EValue $ LInteger 1)
+                    )
+                )
+              )
   , operatorPrecedences
   ]
 
@@ -319,18 +381,20 @@
               )
   , testCase "< && starts_with" $
       parseExpression " 2 < $test && $var2.starts_with(\"test\") && true " @?=
-        Right (EBinary And
-                 (EBinary And
+        Right (EBinary LazyAnd
+                 (EBinary LazyAnd
                     (EBinary LessThan
                        (EValue $ LInteger 2)
                        (EValue $ Variable "test")
                     )
-                    (EBinary Prefix
-                       (EValue $ Variable "var2")
-                       (EValue $ LString "test")
+                    (EClosure []
+                      (EBinary Prefix
+                        (EValue $ Variable "var2")
+                        (EValue $ LString "test")
+                      )
                     )
                  )
-                 (EValue $ LBool True)
+                 (EClosure [] (EValue $ LBool True))
               )
   , testCase "+ *" $
       parseExpression "1 + 2 * 3" @?=
@@ -406,18 +470,24 @@
       parseCheck "check if true" @?=
         Right Check
           { cQueries = [QueryItem [] [EValue $ LBool True] []]
-          , cKind = One
+          , cKind = CheckOne
           }
   , testCase "Simple check all" $
       parseCheck "check all true" @?=
         Right Check
           { cQueries = [QueryItem [] [EValue $ LBool True] []]
-          , cKind = All
+          , cKind = CheckAll
           }
+  , testCase "Simple reject if" $
+      parseCheck "reject if true" @?=
+        Right Check
+          { cQueries = [QueryItem [] [EValue $ LBool True] []]
+          , cKind = Reject
+          }
   , testCase "Multiple groups" $
       parseCheck
-        "check if fact($var), $var == true or \
-        \other($var), $var == 2" @?=
+        "check if fact($var), $var === true or \
+        \other($var), $var === 2" @?=
           Right Check
             { cQueries =
                 [ QueryItem [Predicate "fact" [Variable "var"]]
@@ -427,12 +497,12 @@
                             [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))]
                             []
                 ]
-            , cKind = One
+            , cKind = CheckOne
             }
   , testCase "Multiple check all groups" $
       parseCheck
-        "check all fact($var), $var == true or \
-        \other($var), $var == 2" @?=
+        "check all fact($var), $var === true or \
+        \other($var), $var === 2" @?=
           Right Check
             { cQueries =
                 [ QueryItem [Predicate "fact" [Variable "var"]]
@@ -442,12 +512,27 @@
                             [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))]
                             []
                 ]
-            , cKind = All
+            , cKind = CheckAll
             }
+  , testCase "Multiple reject if groups" $
+      parseCheck
+        "reject if fact($var), $var === true or \
+        \other($var), $var === 2" @?=
+          Right Check
+            { cQueries =
+                [ QueryItem [Predicate "fact" [Variable "var"]]
+                            [EBinary Equal (EValue (Variable "var")) (EValue (LBool True))]
+                            []
+                , QueryItem [Predicate "other" [Variable "var"]]
+                            [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))]
+                            []
+                ]
+            , cKind = Reject
+            }
   , testCase "Multiple groups, scoped" $
       parseCheck
-        "check if fact($var), $var == true trusting previous or \
-        \other($var), $var == 2 trusting authority" @?=
+        "check if fact($var), $var === true trusting previous or \
+        \other($var), $var === 2 trusting authority" @?=
           Right Check
             { cQueries =
                 [ QueryItem [Predicate "fact" [Variable "var"]]
@@ -457,12 +542,12 @@
                             [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))]
                             [OnlyAuthority]
                 ]
-            , cKind = One
+            , cKind = CheckOne
             }
   , testCase "Multiple check all groups, scoped" $
       parseCheck
-        "check all fact($var), $var == true trusting previous or \
-        \other($var), $var == 2 trusting authority" @?=
+        "check all fact($var), $var === true trusting previous or \
+        \other($var), $var === 2 trusting authority" @?=
           Right Check
             { cQueries =
                 [ QueryItem [Predicate "fact" [Variable "var"]]
@@ -472,7 +557,7 @@
                             [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))]
                             [OnlyAuthority]
                 ]
-            , cKind = All
+            , cKind = CheckAll
             }
   ]
 
@@ -486,8 +571,8 @@
         Right (Deny, [QueryItem [] [EValue $ LBool True] []])
   , testCase "Allow with multiple groups" $
       parsePolicy
-        "allow if fact($var), $var == true or \
-        \other($var), $var == 2" @?=
+        "allow if fact($var), $var === true or \
+        \other($var), $var === 2" @?=
           Right
             ( Allow
             , [ QueryItem [Predicate "fact" [Variable "var"]]
@@ -500,8 +585,8 @@
             )
   , testCase "Deny with multiple groups" $
       parsePolicy
-        "deny if fact($var), $var == true or \
-        \other($var), $var == 2" @?=
+        "deny if fact($var), $var === true or \
+        \other($var), $var === 2" @?=
           Right
             ( Deny
             , [ QueryItem [Predicate "fact" [Variable "var"]]
@@ -515,8 +600,8 @@
   , testCase "Deny with multiple groups, multiline" $
       parsePolicy
         "deny if\n\
-           \fact($var), $var == true or //comment\n\
-           \other($var), $var == 2" @?=
+           \fact($var), $var === true or //comment\n\
+           \other($var), $var === 2" @?=
           Right
             ( Deny
             , [ QueryItem [Predicate "fact" [Variable "var"]]
@@ -529,8 +614,8 @@
             )
   , testCase "Allow with multiple groups, scoped" $
       parsePolicy
-        "allow if fact($var), $var == true trusting authority or \
-        \other($var), $var == 2 trusting ed25519/a1b712761c609039f878edad694d762652f1548a68acccc96735b3196a240e8b,ed25519/083aae4ba29a9a3781cdee7a800f4f8ab90591f65ca983fc429687628311aedd,ed25519/c6864578bc03596d52878bd70025ec966c95c60727cb6573198453e82132510d " @?=
+        "allow if fact($var), $var === true trusting authority or \
+        \other($var), $var === 2 trusting ed25519/a1b712761c609039f878edad694d762652f1548a68acccc96735b3196a240e8b,ed25519/083aae4ba29a9a3781cdee7a800f4f8ab90591f65ca983fc429687628311aedd,ed25519/c6864578bc03596d52878bd70025ec966c95c60727cb6573198453e82132510d " @?=
           Right
             ( Allow
             , [ QueryItem [Predicate "fact" [Variable "var"]]
@@ -592,7 +677,7 @@
             \  user($user_id),\n\
             \  member($user_id, $team_id),\n\
             \  team_role($team_id, $blog_id, \"contributor\"),\n\
-            \  [\"read\", \"write\"].contains($operation);\n\
+            \  {\"read\", \"write\"}.contains($operation);\n\
             \// unauthenticated users have read access on published articles\n\
             \allow if\n\
             \  operation(\"read\"),\n\
@@ -681,7 +766,7 @@
         \  user($user_id),\n\
         \  member($user_id, $team_id),\n\
         \  team_role($team_id, $blog_id, \"contributor\"),\n\
-        \  [\"read\", \"write\"].contains($operation);\n\
+        \  {\"read\", \"write\"}.contains($operation);\n\
         \ "
       p = Predicate
       sRead = LString "read"
diff --git a/test/Spec/Quasiquoter.hs b/test/Spec/Quasiquoter.hs
--- a/test/Spec/Quasiquoter.hs
+++ b/test/Spec/Quasiquoter.hs
@@ -1,6 +1,10 @@
 {-# LANGUAGE OverloadedLists   #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes       #-}
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Spec.Quasiquoter (specs) where
 
 import           Data.Text                   (Text)
diff --git a/test/Spec/Roundtrip.hs b/test/Spec/Roundtrip.hs
--- a/test/Spec/Roundtrip.hs
+++ b/test/Spec/Roundtrip.hs
@@ -3,6 +3,10 @@
 {-# LANGUAGE QuasiQuotes       #-}
 {-# LANGUAGE RecordWildCards   #-}
 {-# LANGUAGE TupleSections     #-}
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Spec.Roundtrip
   ( specs
   ) where
@@ -58,7 +62,7 @@
   final <- addBlocks blocks' init'
   let serialized = s final
       parsed = p pk serialized
-      getBlock ((_, b), _, _, _) = b
+      getBlock ((_, b), _, _, _, _) = b
       getBlocks b = getBlock <$> authority b :| blocks b
   getBlocks <$> parsed @?= Right (snd <$> i)
   rootKeyId <$> parsed @?= Right (Just 1)
@@ -87,7 +91,7 @@
   final <- addBlocks blocks' init'
   let serialized = s final
       parsed = p pk serialized
-      getBlock ((_, b), _, _, _) = b
+      getBlock ((_, b), _, _, _, _) = b
       getBlocks b = getBlock <$> authority b :| blocks b
   getBlocks <$> parsed @?= Right (snd <$> i)
   rootKeyId <$> parsed @?= Right (Just 1)
@@ -109,7 +113,7 @@
     |] :|
   [ [block|
       valid_date("file1") <- time($0), resource("file1"), $0 <= 2030-12-31T12:59:59+00:00;
-      valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59+00:00, !["file1"].contains($1);
+      valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59+00:00, !{"file1"}.contains($1);
       check if valid_date($0), resource($0);
     |]
   , [block|
@@ -123,25 +127,25 @@
       check if 1 <= 1;
       check if 2 >= 1;
       check if 2 >= 2;
-      check if 3 == 3;
-      check if 1 + 2 * 3 - 4 / 2 == 5;
+      check if 3 === 3;
+      check if 1 + 2 * 3 - 4 / 2 === 5;
       check if "hello world".starts_with("hello") && "hello world".ends_with("world");
       check if "aaabde".matches("a*c?.e");
-      check if "abcD12" == "abcD12";
+      check if "abcD12" === "abcD12";
       check if 2019-12-04T09:46:41+00:00 < 2020-12-04T09:46:41+00:00;
       check if 2020-12-04T09:46:41+00:00 > 2019-12-04T09:46:41+00:00;
       check if 2019-12-04T09:46:41+00:00 <= 2020-12-04T09:46:41+00:00;
       check if 2020-12-04T09:46:41+00:00 >= 2020-12-04T09:46:41+00:00;
       check if 2020-12-04T09:46:41+00:00 >= 2019-12-04T09:46:41+00:00;
       check if 2020-12-04T09:46:41+00:00 >= 2020-12-04T09:46:41+00:00;
-      check if 2020-12-04T09:46:41+00:00 == 2020-12-04T09:46:41+00:00;
-      check if hex:12ab == hex:12ab;
-      check if [1, 2].contains(2);
-      check if [2019-12-04T09:46:41+00:00, 2020-12-04T09:46:41+00:00].contains(2020-12-04T09:46:41+00:00);
-      check if [false, true].contains(true);
-      check if ["abc", "def"].contains("abc");
-      check if [hex:12ab, hex:34de].contains(hex:34de);
-      check if ["hello", "world"].contains("hello");
+      check if 2020-12-04T09:46:41+00:00 === 2020-12-04T09:46:41+00:00;
+      check if hex:12ab === hex:12ab;
+      check if {1, 2}.contains(2);
+      check if {2019-12-04T09:46:41+00:00, 2020-12-04T09:46:41+00:00}.contains(2020-12-04T09:46:41+00:00);
+      check if {false, true}.contains(true);
+      check if {"abc", "def"}.contains("abc");
+      check if {hex:12ab, hex:34de}.contains(hex:34de);
+      check if {"hello", "world"}.contains("hello");
     |]
   , [block|
       check if
diff --git a/test/Spec/SampleReader.hs b/test/Spec/SampleReader.hs
--- a/test/Spec/SampleReader.hs
+++ b/test/Spec/SampleReader.hs
@@ -10,6 +10,10 @@
 {-# LANGUAGE OverloadedStrings     #-}
 {-# LANGUAGE RecordWildCards       #-}
 {-# LANGUAGE TypeApplications      #-}
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Spec.SampleReader where
 
 import           Control.Arrow                 ((&&&))
@@ -39,6 +43,7 @@
 
 import           Auth.Biscuit
 import           Auth.Biscuit.Datalog.AST      (renderAuthorizer, renderBlock)
+import qualified Auth.Biscuit.Datalog.AST      as AST
 import           Auth.Biscuit.Datalog.Executor (ExecutionError (..),
                                                 ResultError (..))
 import           Auth.Biscuit.Datalog.Parser   (authorizerParser, blockParser)
@@ -48,7 +53,7 @@
 import           Spec.Parser                   (parseAuthorizer, parseBlock)
 
 getB :: ParsedSignedBlock -> Block
-getB ((_, b), _, _, _) = b
+getB ((_, b), _, _, _, _) = b
 
 getAuthority :: Biscuit p Verified -> Block
 getAuthority = getB . authority
@@ -118,7 +123,7 @@
   = ValidationR
   { world           :: Maybe WorldDesc
   , result          :: RustResult RustError Int
-  , authorizer_code :: Authorizer
+  , authorizer_code :: Text
   , revocation_ids  :: [Text]
   } deriving stock (Eq, Show, Generic)
     deriving anyclass (FromJSON, ToJSON)
@@ -147,8 +152,11 @@
 
 data BlockDesc
   = BlockDesc
-  { symbols :: [Text]
-  , code    :: Text
+  { symbols      :: [Text]
+  , code         :: Text
+  , public_keys  :: [Text]
+  , external_key :: Maybe Text
+  , version      :: Int
   }
   deriving stock (Eq, Show, Generic)
   deriving anyclass (FromJSON, ToJSON)
@@ -220,11 +228,10 @@
 processTestCase :: (String -> IO ())
                 -> PublicKey -> TestCase (FilePath, ByteString)
                 -> Assertion
-processTestCase step rootPk TestCase{..} =
-  if fst filename == "test018_unbound_variables_in_rule.bc"
-  then
-    step "Skipping for now (unbound variables are now caught before evaluation)"
-  else do
+processTestCase step rootPk TestCase{..}
+  | fst filename == "test018_unbound_variables_in_rule.bc" = step "Skipping for now (unbound variables are now caught before evaluation)"
+  | fst filename `elem` ["test036_secp256r1.bc", "test037_secp256r1_third_party.bc"] = step "Skipping for now (not supported yet)"
+  | otherwise = do
     step "Parsing "
     let vList = Map.toList validations
     case parse rootPk (snd filename) of
@@ -258,6 +265,7 @@
           mustMatchEither
             [ key "Format" . key "Signature" . key "InvalidSignature"
             , key "Format" . key "InvalidSignatureSize"
+            , key "Format" . key "BlockSignatureDeserializationError"
             ]
         InvalidProof ->
           assertFailure $ "InvalidProof can't appear here " <> show re
@@ -295,7 +303,15 @@
   when (name /= "") $ step ("Checking " <> name)
   let w = fold world
   pols <- either (assertFailure . show) pure $ parseAuthorizer $ foldMap (<> ";") (policies w)
-  res <- authorizeBiscuit b (authorizer_code <> pols)
+  authorizer <- either (assertFailure . show)  pure $ parseAuthorizer authorizer_code
+  let testfn :: AST.Value -> Maybe AST.Value -> Either String AST.Value
+      testfn v Nothing = Right v
+      testfn (LString x) (Just (LString y))
+        | x == y = Right $ LString "equal strings"
+        | otherwise = Right $ LString "different strings"
+      testfn _ _ = Left "unsupported operands"
+  let limits = withExternFunc "test" testfn defaultLimits
+  res <- authorizeBiscuitWithLimits limits b (authorizer <> pols)
   checkResult compareExecErrors result res
   let revocationIds = encodeHex <$> toList (getRevocationIds b)
   step "Comparing revocation ids"
@@ -318,29 +334,3 @@
   SampleFile{..} <- readSamplesFile
   pure $ testGroup "Biscuit samples - compliance checks"
        $ mkTestCase root_public_key <$> testcases
-mkTestCaseFromBiscuit
-  :: String
-  -> FilePath
-  -> Biscuit Open Verified
-  -> [(String, Authorizer)]
-  -> IO (TestCase FilePath)
-mkTestCaseFromBiscuit title filename biscuit authorizers = do
-  let mkBlockDesc :: Block -> BlockDesc
-      mkBlockDesc b = BlockDesc
-        { code = renderBlock b
-        , symbols = []
-        }
-      mkValidation :: Authorizer -> IO ValidationR
-      mkValidation authorizer = do
-        Right success <- authorizeBiscuit biscuit authorizer
-        pure ValidationR
-          { world = Just mempty
-          , result = Ok 0
-          , authorizer_code = authorizer
-          , revocation_ids = encodeHex <$> toList (getRevocationIds biscuit)
-          }
-  BS.writeFile ("test/samples/current/" <> filename) (serialize biscuit)
-  let token = mkBlockDesc <$> getAuthority biscuit :| getBlocks biscuit
-  validations <- Map.fromList <$> traverse (traverse mkValidation) authorizers
-
-  pure TestCase{..}
diff --git a/test/Spec/ScopedExecutor.hs b/test/Spec/ScopedExecutor.hs
--- a/test/Spec/ScopedExecutor.hs
+++ b/test/Spec/ScopedExecutor.hs
@@ -1,14 +1,20 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes       #-}
 {- HLINT ignore "Reduce duplication" -}
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Spec.ScopedExecutor (specs) where
 
 import           Control.Arrow                       ((&&&))
+import           Control.Concurrent.MVar              (newEmptyMVar, takeMVar, putMVar)
 import           Data.Either                         (isRight)
 import           Data.Map.Strict                     as Map
 import           Data.Set                            as Set
 import           Data.Text                           (Text, unpack)
-import           Test.Tasty
+import           System.IO.Unsafe                    (unsafePerformIO)
+import           Test.Tasty                   hiding (Timeout)
 import           Test.Tasty.HUnit
 
 import           Auth.Biscuit                        (addBlock, addSignedBlock,
@@ -21,7 +27,8 @@
 import           Auth.Biscuit.Datalog.Executor       (ExecutionError (..),
                                                       Limits (..),
                                                       ResultError (..),
-                                                      defaultLimits)
+                                                      defaultLimits,
+                                                      withExternFunc)
 import           Auth.Biscuit.Datalog.Parser         (authorizer, block, check,
                                                       query, run)
 import           Auth.Biscuit.Datalog.ScopedExecutor
@@ -36,6 +43,7 @@
   , thirdPartyBlocks
   , iterationCountWorks
   , maxFactsCountWorks
+  , maxTimeWorks
   , allChecksAreCollected
   , revocationIdsAreInjected
   , authorizerFactsAreQueried
@@ -219,6 +227,22 @@
          allow if true;
        |]
   runAuthorizerNoTimeout limits (authority, "", Nothing) [(block1, "", Nothing)] verif @?= Left TooManyFacts
+
+maxTimeWorks :: TestTree
+maxTimeWorks = testCase "ScopedExecutions stops when hitting the timeout" $ do
+  lateBoolVar <- newEmptyMVar
+  let slowBool _ _ = unsafePerformIO $ Right <$> takeMVar lateBoolVar
+      limits = withExternFunc "foo" slowBool $ defaultLimits { maxTime = 1 }
+      authority =
+       [block|
+       |]
+      verif =
+       [authorizer|
+         allow if 1.extern::foo();
+       |]
+  res <- runAuthorizerWithLimits limits (authority, "", Nothing) [] verif
+  putMVar lateBoolVar $ LBool True
+  res @?= Left Timeout
 
 allChecksAreCollected :: TestTree
 allChecksAreCollected = testCase "ScopedExecutions collects all facts results even after a failure" $ do
diff --git a/test/Spec/Verification.hs b/test/Spec/Verification.hs
--- a/test/Spec/Verification.hs
+++ b/test/Spec/Verification.hs
@@ -1,6 +1,10 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes       #-}
 {-# LANGUAGE RecordWildCards   #-}
+{-
+  Copyright   : © Clément Delafargue, 2021
+  License     : BSD-3-Clause
+-}
 module Spec.Verification
   ( specs
   ) where
@@ -46,7 +50,7 @@
 ifFalse' :: Check
 ifFalse' = Check
   { cQueries = matchedQuery ifFalse
-  , cKind = One
+  , cKind = CheckOne
   }
 
 checkAll' :: Check
diff --git a/test/samples/current/samples.json b/test/samples/current/samples.json
--- a/test/samples/current/samples.json
+++ b/test/samples/current/samples.json
@@ -13,2175 +13,3295 @@
           ],
           "public_keys": [],
           "external_key": null,
-          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"
-        },
-        {
-          "symbols": [
-            "0"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "resource(\"file1\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "right(\"file1\", \"read\")",
-                  "right(\"file1\", \"write\")",
-                  "right(\"file2\", \"read\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 1,
-                "checks": [
-                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 1,
-                        "check_id": 0,
-                        "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file1\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "7595a112a1eb5b81a6e398852e6118b7f5b8cbbff452778e655100e5fb4faa8d3a2af52fe2c4f9524879605675fae26adbc4783e0cafc43522fa82385f396c03",
-            "45f4c14f9d9e8fa044d68be7a2ec8cddb835f575c7b913ec59bd636c70acae9a90db9064ba0b3084290ed0c422bbb7170092a884f5e0202b31e9235bbcc1650d"
-          ]
-        }
-      }
-    },
-    {
-      "title": "different root key",
-      "filename": "test002_different_root_key.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\n"
-        },
-        {
-          "symbols": [
-            "0"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": null,
-          "result": {
-            "Err": {
-              "Format": {
-                "Signature": {
-                  "InvalidSignature": "signature error: Verification equation was not satisfied"
-                }
-              }
-            }
-          },
-          "authorizer_code": "",
-          "revocation_ids": []
-        }
-      }
-    },
-    {
-      "title": "invalid signature format",
-      "filename": "test003_invalid_signature_format.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1",
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"
-        },
-        {
-          "symbols": [
-            "0"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": null,
-          "result": {
-            "Err": {
-              "Format": {
-                "InvalidSignatureSize": 16
-              }
-            }
-          },
-          "authorizer_code": "",
-          "revocation_ids": []
-        }
-      }
-    },
-    {
-      "title": "random block",
-      "filename": "test004_random_block.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1",
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"
-        },
-        {
-          "symbols": [
-            "0"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": null,
-          "result": {
-            "Err": {
-              "Format": {
-                "Signature": {
-                  "InvalidSignature": "signature error: Verification equation was not satisfied"
-                }
-              }
-            }
-          },
-          "authorizer_code": "",
-          "revocation_ids": []
-        }
-      }
-    },
-    {
-      "title": "invalid signature",
-      "filename": "test005_invalid_signature.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1",
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"
-        },
-        {
-          "symbols": [
-            "0"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": null,
-          "result": {
-            "Err": {
-              "Format": {
-                "Signature": {
-                  "InvalidSignature": "signature error: Verification equation was not satisfied"
-                }
-              }
-            }
-          },
-          "authorizer_code": "",
-          "revocation_ids": []
-        }
-      }
-    },
-    {
-      "title": "reordered blocks",
-      "filename": "test006_reordered_blocks.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1",
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"
-        },
-        {
-          "symbols": [
-            "0"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        },
-        {
-          "symbols": [],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource(\"file1\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": null,
-          "result": {
-            "Err": {
-              "Format": {
-                "Signature": {
-                  "InvalidSignature": "signature error: Verification equation was not satisfied"
-                }
-              }
-            }
-          },
-          "authorizer_code": "",
-          "revocation_ids": []
-        }
-      }
-    },
-    {
-      "title": "scoped rules",
-      "filename": "test007_scoped_rules.bc",
-      "token": [
-        {
-          "symbols": [
-            "user_id",
-            "alice",
-            "file1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "user_id(\"alice\");\nowner(\"alice\", \"file1\");\n"
-        },
-        {
-          "symbols": [
-            "0",
-            "1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        },
-        {
-          "symbols": [
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "owner(\"alice\", \"file2\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"read\")",
-                  "resource(\"file2\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "owner(\"alice\", \"file1\")",
-                  "user_id(\"alice\")"
-                ]
-              },
-              {
-                "origin": [
-                  2
-                ],
-                "facts": [
-                  "owner(\"alice\", \"file2\")"
-                ]
-              }
-            ],
-            "rules": [
-              {
-                "origin": 1,
-                "rules": [
-                  "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0)"
-                ]
-              }
-            ],
-            "checks": [
-              {
-                "origin": 1,
-                "checks": [
-                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 1,
-                        "check_id": 0,
-                        "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "4d86c9af808dc2e0583f47282e6f5df3e09dc264d5231ec360b4519e15ddaeec60b25a9bbcb22e8d192f4d36a0da3f9243711e30535b00ee55c53cb1395f230a",
-            "63208c668c66f3ba6927140ba37533593b25e03459447805d4b2a8b75adeef45794c3d7249afe506ed77ccee276160bb4052a4009302bd34871a440f070b4509",
-            "d8da982888eae8c038e4894a8c06fc57d8e5f06ad2e972b9cf4bde49ad60804558a0d1938192596c702d8e4f7f12ec19201d7c33d0cd77774a0d879a33880d02"
-          ]
-        }
-      }
-    },
-    {
-      "title": "scoped checks",
-      "filename": "test008_scoped_checks.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\n"
-        },
-        {
-          "symbols": [
-            "0"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        },
-        {
-          "symbols": [
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file2\", \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"read\")",
-                  "resource(\"file2\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "right(\"file1\", \"read\")"
-                ]
-              },
-              {
-                "origin": [
-                  2
-                ],
-                "facts": [
-                  "right(\"file2\", \"read\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 1,
-                "checks": [
-                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 1,
-                        "check_id": 0,
-                        "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00",
-            "77df45442be86a416aa02fd9d98d6d4703c634a9e3b1d293b41f5dc97849afbe7faeec8c22a210574888acc008fb64fe691ec9e8d2655586f970d9a6b6577000",
-            "b31398aefe97d3db41ebc445760f216fb3aa7bf7439adcfc3a07489bfcc163970af3f4e20f5460aa24cf841101a5ab114d21acc0ee8d442bae7793b121284900"
-          ]
-        }
-      }
-    },
-    {
-      "title": "expired token",
-      "filename": "test009_expired_token.bc",
-      "token": [
-        {
-          "symbols": [],
-          "public_keys": [],
-          "external_key": null,
-          "code": ""
-        },
-        {
-          "symbols": [
-            "file1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource(\"file1\");\ncheck if time($time), $time <= 2018-12-20T00:00:00Z;\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"read\")",
-                  "resource(\"file1\")",
-                  "time(2020-12-21T09:23:12Z)"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 1,
-                "checks": [
-                  "check if resource(\"file1\")",
-                  "check if time($time), $time <= 2018-12-20T00:00:00Z"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 1,
-                        "check_id": 1,
-                        "rule": "check if time($time), $time <= 2018-12-20T00:00:00Z"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file1\");\noperation(\"read\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",
-          "revocation_ids": [
-            "c248907bb6e5f433bbb5edf6367b399ebefca0d321d0b2ea9fc67f66dc1064ce926adb0c05d90c3e8a2833328b3578f79c4e1bca43583d9bcfb2ba6c37303d00",
-            "a4edf7aaea8658bb9ae19b3ffe2adcc77cc9f16c249aeb0a85a584b5362f89f27f7c67ac0af16d7170673d6d1fb1563d1934b25ec5a461f6c01fa49805cd5e07"
-          ]
-        }
-      }
-    },
-    {
-      "title": "authorizer scope",
-      "filename": "test010_authorizer_scope.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\n"
-        },
-        {
-          "symbols": [
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file2\", \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"read\")",
-                  "resource(\"file2\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "right(\"file1\", \"read\")"
-                ]
-              },
-              {
-                "origin": [
-                  1
-                ],
-                "facts": [
-                  "right(\"file2\", \"read\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 18446744073709551615,
-                "checks": [
-                  "check if right($0, $1), resource($0), operation($1)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Authorizer": {
-                        "check_id": 0,
-                        "rule": "check if right($0, $1), resource($0), operation($1)"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\ncheck if right($0, $1), resource($0), operation($1);\n\nallow if true;\n",
-          "revocation_ids": [
-            "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00",
-            "966eceb2aa937c41b25368808bab6e0698c02a4038de669d007c9c3d43602638a640083558d1576ac80cf3eb2ac6a7585527e0f6c1a65402f0935cf7f4df8005"
-          ]
-        }
-      }
-    },
-    {
-      "title": "authorizer authority checks",
-      "filename": "test011_authorizer_authority_caveats.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"read\")",
-                  "resource(\"file2\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "right(\"file1\", \"read\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 18446744073709551615,
-                "checks": [
-                  "check if right($0, $1), resource($0), operation($1)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Authorizer": {
-                        "check_id": 0,
-                        "rule": "check if right($0, $1), resource($0), operation($1)"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\ncheck if right($0, $1), resource($0), operation($1);\n\nallow if true;\n",
-          "revocation_ids": [
-            "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00"
-          ]
-        }
-      }
-    },
-    {
-      "title": "authority checks",
-      "filename": "test012_authority_caveats.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource(\"file1\");\n"
-        }
-      ],
-      "validations": {
-        "file1": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"read\")",
-                  "resource(\"file1\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if resource(\"file1\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "6a8f90dad67ae2ac188460463914ae7326fda431c80785755f4edcc15f1a53911f7366e606ad80cbbeba94672e42713e88632a932128f1d796ce9ba7d7a0b80a"
-          ]
-        },
-        "file2": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"read\")",
-                  "resource(\"file2\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if resource(\"file1\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 0,
-                        "check_id": 0,
-                        "rule": "check if resource(\"file1\")"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "6a8f90dad67ae2ac188460463914ae7326fda431c80785755f4edcc15f1a53911f7366e606ad80cbbeba94672e42713e88632a932128f1d796ce9ba7d7a0b80a"
-          ]
-        }
-      }
-    },
-    {
-      "title": "block rules",
-      "filename": "test013_block_rules.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1",
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n"
-        },
-        {
-          "symbols": [
-            "valid_date",
-            "0",
-            "1"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z;\nvalid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1);\ncheck if valid_date($0), resource($0);\n"
-        }
-      ],
-      "validations": {
-        "file1": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "resource(\"file1\")",
-                  "time(2020-12-21T09:23:12Z)"
-                ]
-              },
-              {
-                "origin": [
-                  null,
-                  1
-                ],
-                "facts": [
-                  "valid_date(\"file1\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "right(\"file1\", \"read\")",
-                  "right(\"file2\", \"read\")"
-                ]
-              }
-            ],
-            "rules": [
-              {
-                "origin": 1,
-                "rules": [
-                  "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z",
-                  "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)"
-                ]
-              }
-            ],
-            "checks": [
-              {
-                "origin": 1,
-                "checks": [
-                  "check if valid_date($0), resource($0)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "resource(\"file1\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",
-          "revocation_ids": [
-            "c46d071ff3f33434223c8305fdad529f62bf78bb5d9cbfc2a345d4bca6bf314014840e18ba353f86fdb9073d58b12b8c872ac1f8e593c2e9064b90f6c2ede006",
-            "a0c4c163a0b3ca406df4ece3d1371356190df04208eccef72f77e875ed0531b5d37e243d6f388b1967776a5dfd16ef228f19c5bdd6d2820f145c5ed3c3dcdc00"
-          ]
-        },
-        "file2": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "resource(\"file2\")",
-                  "time(2020-12-21T09:23:12Z)"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "right(\"file1\", \"read\")",
-                  "right(\"file2\", \"read\")"
-                ]
-              }
-            ],
-            "rules": [
-              {
-                "origin": 1,
-                "rules": [
-                  "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z",
-                  "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)"
-                ]
-              }
-            ],
-            "checks": [
-              {
-                "origin": 1,
-                "checks": [
-                  "check if valid_date($0), resource($0)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 1,
-                        "check_id": 0,
-                        "rule": "check if valid_date($0), resource($0)"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file2\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",
-          "revocation_ids": [
-            "c46d071ff3f33434223c8305fdad529f62bf78bb5d9cbfc2a345d4bca6bf314014840e18ba353f86fdb9073d58b12b8c872ac1f8e593c2e9064b90f6c2ede006",
-            "a0c4c163a0b3ca406df4ece3d1371356190df04208eccef72f77e875ed0531b5d37e243d6f388b1967776a5dfd16ef228f19c5bdd6d2820f145c5ed3c3dcdc00"
-          ]
-        }
-      }
-    },
-    {
-      "title": "regex_constraint",
-      "filename": "test014_regex_constraint.bc",
-      "token": [
-        {
-          "symbols": [
-            "0",
-            "file[0-9]+.txt"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), $0.matches(\"file[0-9]+.txt\");\n"
-        }
-      ],
-      "validations": {
-        "file1": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "resource(\"file1\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if resource($0), $0.matches(\"file[0-9]+.txt\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 0,
-                        "check_id": 0,
-                        "rule": "check if resource($0), $0.matches(\"file[0-9]+.txt\")"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "resource(\"file1\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "da42718ad2631c12d3a44b7710dcc76c6c7809c6bc3a2d7eb0378c4154eae10e0884a8d54a2cd25ca3dfe01091d816ebbb9d246227baf7a359a787cb2344ad07"
-          ]
-        },
-        "file123": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "resource(\"file123.txt\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if resource($0), $0.matches(\"file[0-9]+.txt\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "resource(\"file123.txt\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "da42718ad2631c12d3a44b7710dcc76c6c7809c6bc3a2d7eb0378c4154eae10e0884a8d54a2cd25ca3dfe01091d816ebbb9d246227baf7a359a787cb2344ad07"
-          ]
-        }
-      }
-    },
-    {
-      "title": "multi queries checks",
-      "filename": "test015_multi_queries_caveats.bc",
-      "token": [
-        {
-          "symbols": [
-            "must_be_present",
-            "hello"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "must_be_present(\"hello\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "must_be_present(\"hello\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 18446744073709551615,
-                "checks": [
-                  "check if must_be_present($0) or must_be_present($0)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "check if must_be_present($0) or must_be_present($0);\n\nallow if true;\n",
-          "revocation_ids": [
-            "b0d466d31e015fa85a075fa875f7e1c9017edd503fee9f62a5f033e1fcfa811074b6e39dfe5af2f452043db97a3f98650592a370f5685b62c5d6abf9dd10b603"
-          ]
-        }
-      }
-    },
-    {
-      "title": "check head name should be independent from fact names",
-      "filename": "test016_caveat_head_name.bc",
-      "token": [
-        {
-          "symbols": [
-            "hello"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource(\"hello\");\n"
-        },
-        {
-          "symbols": [
-            "test"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "query(\"test\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  1
-                ],
-                "facts": [
-                  "query(\"test\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if resource(\"hello\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 0,
-                        "check_id": 0,
-                        "rule": "check if resource(\"hello\")"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "allow if true;\n",
-          "revocation_ids": [
-            "ce6f804f4390e693a8853d9a4a10bd4f3c94b86b7c6d671993a6e19346bc4d20bbb52cc945e5d0d02e4e75fa5da2caa99764050190353564a0a0b4b276809402",
-            "916d566cc724e0773046fc5266e9d0d804311435b8d6955b332f823ab296be9a78dfea190447732ac9f6217234cf5726becf88f65169c6de56a766af55451b0f"
-          ]
-        }
-      }
-    },
-    {
-      "title": "test expression syntax and all available operations",
-      "filename": "test017_expressions.bc",
-      "token": [
-        {
-          "symbols": [
-            "hello world",
-            "hello",
-            "world",
-            "aaabde",
-            "a*c?.e",
-            "abd",
-            "aaa",
-            "b",
-            "de",
-            "abcD12",
-            "é",
-            "abc",
-            "def"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if true;\ncheck if !false;\ncheck if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if true == true;\ncheck if false == false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 == 3;\ncheck if 1 + 2 * 3 - 4 / 2 == 5;\ncheck if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" == \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12\".length() == 6;\ncheck if \"é\".length() == 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z;\ncheck if hex:12ab == hex:12ab;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] == [1, 2];\ncheck if [1, 2].intersection([2, 3]) == [2];\ncheck if [1, 2].union([2, 3]) == [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() == 2;\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if !false",
-                  "check if !false && true",
-                  "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"",
-                  "check if \"aaabde\".contains(\"abd\")",
-                  "check if \"aaabde\".matches(\"a*c?.e\")",
-                  "check if \"abcD12\" == \"abcD12\"",
-                  "check if \"abcD12\".length() == 6",
-                  "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")",
-                  "check if \"é\".length() == 2",
-                  "check if (true || false) && true",
-                  "check if 1 + 2 * 3 - 4 / 2 == 5",
-                  "check if 1 < 2",
-                  "check if 1 <= 1",
-                  "check if 1 <= 2",
-                  "check if 2 > 1",
-                  "check if 2 >= 1",
-                  "check if 2 >= 2",
-                  "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z",
-                  "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z",
-                  "check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z",
-                  "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z",
-                  "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z",
-                  "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z",
-                  "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z",
-                  "check if 3 == 3",
-                  "check if [\"abc\", \"def\"].contains(\"abc\")",
-                  "check if [1, 2, 3].intersection([1, 2]).contains(1)",
-                  "check if [1, 2, 3].intersection([1, 2]).length() == 2",
-                  "check if [1, 2] == [1, 2]",
-                  "check if [1, 2].contains(2)",
-                  "check if [1, 2].contains([2])",
-                  "check if [1, 2].intersection([2, 3]) == [2]",
-                  "check if [1, 2].union([2, 3]) == [1, 2, 3]",
-                  "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)",
-                  "check if [false, true].contains(true)",
-                  "check if [hex:12ab, hex:34de].contains(hex:34de)",
-                  "check if false == false",
-                  "check if false || true",
-                  "check if hex:12ab == hex:12ab",
-                  "check if true",
-                  "check if true == true"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "allow if true;\n",
-          "revocation_ids": [
-            "3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c"
-          ]
-        }
-      }
-    },
-    {
-      "title": "invalid block rule with unbound_variables",
-      "filename": "test018_unbound_variables_in_rule.bc",
-      "token": [
-        {
-          "symbols": [],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if operation(\"read\");\n"
-        },
-        {
-          "symbols": [
-            "unbound",
-            "any1",
-            "any2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "operation($unbound, \"read\") <- operation($any1, $any2);\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": null,
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "InvalidBlockRule": [
-                  0,
-                  "operation($unbound, \"read\") <- operation($any1, $any2)"
-                ]
-              }
-            }
-          },
-          "authorizer_code": "",
-          "revocation_ids": [
-            "a44210c6a01e55eadefc7d8540c2e6eff80ab6eeedde4751de734f9d780435780680d3f42d826b7e0f0dcf4a5ba303fd4c116984bb30978813d46ed867924307",
-            "b0a33e3f4cd0994c0766c196c4d11c15e5a0f9bfba79a3a2b35ddd04ddb890282a7c63336ada5c680b9f9c940c1fa7127d2699754cbc77c21e1a2d85c5ef700c"
-          ]
-        }
-      }
-    },
-    {
-      "title": "invalid block rule generating an #authority or #ambient symbol with a variable",
-      "filename": "test019_generating_ambient_from_variables.bc",
-      "token": [
-        {
-          "symbols": [],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if operation(\"read\");\n"
-        },
-        {
-          "symbols": [
-            "any"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "operation(\"read\") <- operation($any);\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"write\")"
-                ]
-              },
-              {
-                "origin": [
-                  null,
-                  1
-                ],
-                "facts": [
-                  "operation(\"read\")"
-                ]
-              }
-            ],
-            "rules": [
-              {
-                "origin": 1,
-                "rules": [
-                  "operation(\"read\") <- operation($any)"
-                ]
-              }
-            ],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if operation(\"read\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 0,
-                        "check_id": 0,
-                        "rule": "check if operation(\"read\")"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "operation(\"write\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "a44210c6a01e55eadefc7d8540c2e6eff80ab6eeedde4751de734f9d780435780680d3f42d826b7e0f0dcf4a5ba303fd4c116984bb30978813d46ed867924307",
-            "d3f8822a9b9bc0ee3933283c493ca9e711be5dd8339b5fe2eba1de3805aad4e84d3e2fb4affb4a743f1289915c167582b9425343635e45b70573ea1ee7a1ea03"
-          ]
-        }
-      }
-    },
-    {
-      "title": "sealed token",
-      "filename": "test020_sealed.bc",
-      "token": [
-        {
-          "symbols": [
-            "file1",
-            "file2"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"
-        },
-        {
-          "symbols": [
-            "0"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"read\")",
-                  "resource(\"file1\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "right(\"file1\", \"read\")",
-                  "right(\"file1\", \"write\")",
-                  "right(\"file2\", \"read\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 1,
-                "checks": [
-                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "7595a112a1eb5b81a6e398852e6118b7f5b8cbbff452778e655100e5fb4faa8d3a2af52fe2c4f9524879605675fae26adbc4783e0cafc43522fa82385f396c03",
-            "45f4c14f9d9e8fa044d68be7a2ec8cddb835f575c7b913ec59bd636c70acae9a90db9064ba0b3084290ed0c422bbb7170092a884f5e0202b31e9235bbcc1650d"
-          ]
-        }
-      }
-    },
-    {
-      "title": "parsing",
-      "filename": "test021_parsing.bc",
-      "token": [
-        {
-          "symbols": [
-            "ns::fact_123",
-            "hello é\t😁"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "ns::fact_123(\"hello é\t😁\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "ns::fact_123(\"hello é\t😁\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 18446744073709551615,
-                "checks": [
-                  "check if ns::fact_123(\"hello é\t😁\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "check if ns::fact_123(\"hello é\t😁\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "d4b2f417b6e906434fdf5058afcabfcb98d3628f814f1c9dd7e64250d9beec4465aff51bd0cb2e85d0e67dc9f613c2a42af6158c678bc6f8b4684cd3a2d0d302"
-          ]
-        }
-      }
-    },
-    {
-      "title": "default_symbols",
-      "filename": "test022_default_symbols.bc",
-      "token": [
-        {
-          "symbols": [],
-          "public_keys": [],
-          "external_key": null,
-          "code": "read(0);\nwrite(1);\nresource(2);\noperation(3);\nright(4);\ntime(5);\nrole(6);\nowner(7);\ntenant(8);\nnamespace(9);\nuser(10);\nteam(11);\nservice(12);\nadmin(13);\nemail(14);\ngroup(15);\nmember(16);\nip_address(17);\nclient(18);\nclient_ip(19);\ndomain(20);\npath(21);\nversion(22);\ncluster(23);\nnode(24);\nhostname(25);\nnonce(26);\nquery(27);\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "admin(13)",
-                  "client(18)",
-                  "client_ip(19)",
-                  "cluster(23)",
-                  "domain(20)",
-                  "email(14)",
-                  "group(15)",
-                  "hostname(25)",
-                  "ip_address(17)",
-                  "member(16)",
-                  "namespace(9)",
-                  "node(24)",
-                  "nonce(26)",
-                  "operation(3)",
-                  "owner(7)",
-                  "path(21)",
-                  "query(27)",
-                  "read(0)",
-                  "resource(2)",
-                  "right(4)",
-                  "role(6)",
-                  "service(12)",
-                  "team(11)",
-                  "tenant(8)",
-                  "time(5)",
-                  "user(10)",
-                  "version(22)",
-                  "write(1)"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 18446744073709551615,
-                "checks": [
-                  "check if read(0), write(1), resource(2), operation(3), right(4), time(5), role(6), owner(7), tenant(8), namespace(9), user(10), team(11), service(12), admin(13), email(14), group(15), member(16), ip_address(17), client(18), client_ip(19), domain(20), path(21), version(22), cluster(23), node(24), hostname(25), nonce(26), query(27)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "check if read(0), write(1), resource(2), operation(3), right(4), time(5), role(6), owner(7), tenant(8), namespace(9), user(10), team(11), service(12), admin(13), email(14), group(15), member(16), ip_address(17), client(18), client_ip(19), domain(20), path(21), version(22), cluster(23), node(24), hostname(25), nonce(26), query(27);\n\nallow if true;\n",
-          "revocation_ids": [
-            "75ce48d496fd28f99905901783a1ba46d7ff8d69f9d364d1546fd73006026eae51849ad1190a4ae521a0a1269f9c6951e226afba8fcd24fa50f679162439ae09"
-          ]
-        }
-      }
-    },
-    {
-      "title": "execution scope",
-      "filename": "test023_execution_scope.bc",
-      "token": [
-        {
-          "symbols": [
-            "authority_fact"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "authority_fact(1);\n"
-        },
-        {
-          "symbols": [
-            "block1_fact"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "block1_fact(1);\n"
-        },
-        {
-          "symbols": [
-            "var"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if authority_fact($var);\ncheck if block1_fact($var);\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "authority_fact(1)"
-                ]
-              },
-              {
-                "origin": [
-                  1
-                ],
-                "facts": [
-                  "block1_fact(1)"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 2,
-                "checks": [
-                  "check if authority_fact($var)",
-                  "check if block1_fact($var)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 2,
-                        "check_id": 1,
-                        "rule": "check if block1_fact($var)"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "allow if true;\n",
-          "revocation_ids": [
-            "f9b49866caef5ece7be14ec5a9b36d98ca81d06b306eb0b4c57cd7436af176f40ee972f40903f87ec4460ab8b1adfcbfa9b19b20a6955a1e8dae7d88b2076005",
-            "889054b9119e4440e54da1b63266a98d0f6646cde195fef206efd8b133cfb2ee7be49b32a9a5925ece452e64f9e6f6d80dab422e916c599675dd68cdea053802",
-            "0a85ffbf27e08aa23665ba0d96a985b274d747556c9f016fd7f590c641ed0e4133291521aa442b320ee9ce80f5ad701b914a0c87b3dfa0cc92629dce94201806"
-          ]
-        }
-      }
-    },
-    {
-      "title": "third party",
-      "filename": "test024_third_party.bc",
-      "token": [
-        {
-          "symbols": [],
-          "public_keys": [
-            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
-          ],
-          "external_key": null,
-          "code": "right(\"read\");\ncheck if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n"
-        },
-        {
-          "symbols": [],
-          "public_keys": [],
-          "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
-          "code": "group(\"admin\");\ncheck if right(\"read\");\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "right(\"read\")"
-                ]
-              },
-              {
-                "origin": [
-                  1
-                ],
-                "facts": [
-                  "group(\"admin\")"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
-                ]
-              },
-              {
-                "origin": 1,
-                "checks": [
-                  "check if right(\"read\")"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "allow if true;\n",
-          "revocation_ids": [
-            "470e4bf7aa2a01ab39c98150bd06aa15b4aa5d86509044a8809a8634cd8cf2b42269a51a774b65d10bac9369d013070b00187925196a8e680108473f11cf8f03",
-            "342167bc54bc642b6718a276875e55b6d39e9b21e4ce13b926a3d398b6c057fc436385bf4c817a16f9ecdf0b0d950e8b8258a20aeb3fd8896c5e9c1f0a53da03"
-          ]
-        }
-      }
-    },
-    {
-      "title": "block rules",
-      "filename": "test025_check_all.bc",
-      "token": [
-        {
-          "symbols": [
-            "allowed_operations",
-            "A",
-            "B",
-            "op",
-            "allowed"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "allowed_operations([\"A\", \"B\"]);\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n"
-        }
-      ],
-      "validations": {
-        "A, B": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"A\")",
-                  "operation(\"B\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "allowed_operations([\"A\", \"B\"])"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "operation(\"A\");\noperation(\"B\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"
-          ]
-        },
-        "A, invalid": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  null
-                ],
-                "facts": [
-                  "operation(\"A\")",
-                  "operation(\"invalid\")"
-                ]
-              },
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "allowed_operations([\"A\", \"B\"])"
-                ]
-              }
-            ],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "FailedLogic": {
-                "Unauthorized": {
-                  "policy": {
-                    "Allow": 0
-                  },
-                  "checks": [
-                    {
-                      "Block": {
-                        "block_id": 0,
-                        "check_id": 0,
-                        "rule": "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"
-                      }
-                    }
-                  ]
-                }
-              }
-            }
-          },
-          "authorizer_code": "operation(\"A\");\noperation(\"invalid\");\n\nallow if true;\n",
-          "revocation_ids": [
-            "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"
-          ]
-        }
-      }
-    },
-    {
-      "title": "public keys interning",
-      "filename": "test026_public_keys_interning.bc",
-      "token": [
-        {
-          "symbols": [],
-          "public_keys": [
-            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
-          ],
-          "external_key": null,
-          "code": "query(0);\ncheck if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n"
-        },
-        {
-          "symbols": [],
-          "public_keys": [
-            "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
-            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
-          ],
-          "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
-          "code": "query(1);\nquery(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n"
-        },
-        {
-          "symbols": [],
-          "public_keys": [
-            "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
-            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
-          ],
-          "external_key": "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
-          "code": "query(2);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n"
-        },
-        {
-          "symbols": [],
-          "public_keys": [
-            "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
-            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
-          ],
-          "external_key": "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
-          "code": "query(3);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n"
-        },
-        {
-          "symbols": [],
-          "public_keys": [
-            "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
-            "ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"
-          ],
-          "external_key": null,
-          "code": "query(4);\ncheck if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136;\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [
-              {
-                "origin": [
-                  0
-                ],
-                "facts": [
-                  "query(0)"
-                ]
-              },
-              {
-                "origin": [
-                  1
-                ],
-                "facts": [
-                  "query(1)"
-                ]
-              },
-              {
-                "origin": [
-                  1,
-                  2
-                ],
-                "facts": [
-                  "query(1, 2)"
-                ]
-              },
-              {
-                "origin": [
-                  2
-                ],
-                "facts": [
-                  "query(2)"
-                ]
-              },
-              {
-                "origin": [
-                  3
-                ],
-                "facts": [
-                  "query(3)"
-                ]
-              },
-              {
-                "origin": [
-                  4
-                ],
-                "facts": [
-                  "query(4)"
-                ]
-              }
-            ],
-            "rules": [
-              {
-                "origin": 1,
-                "rules": [
-                  "query(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
-                ]
-              }
-            ],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
-                ]
-              },
-              {
-                "origin": 1,
-                "checks": [
-                  "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
-                  "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
-                ]
-              },
-              {
-                "origin": 2,
-                "checks": [
-                  "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
-                  "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
-                ]
-              },
-              {
-                "origin": 3,
-                "checks": [
-                  "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
-                  "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
-                ]
-              },
-              {
-                "origin": 4,
-                "checks": [
-                  "check if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
-                  "check if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"
-                ]
-              },
-              {
-                "origin": 18446744073709551615,
-                "checks": [
-                  "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
-                ]
-              }
-            ],
-            "policies": [
-              "deny if query(3)",
-              "deny if query(1, 2)",
-              "deny if query(0) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 3
-          },
-          "authorizer_code": "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\n\ndeny if query(3);\ndeny if query(1, 2);\ndeny if query(0) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\nallow if true;\n",
-          "revocation_ids": [
-            "3771cefe71beb21ead35a59c8116ee82627a5717c0295f35980662abccb159fe1b37848cb1818e548656bd4fd882d0094a2daab631c76b2b72e3a093914bfe04",
-            "6528db2c9a561ada9086268549a600a8a52ff434ea8183812623eec0e9b6c5d3c41ab7868808623021d92294d583afdf92f4354bcdaa1bc50453e1b89afd630d",
-            "5d5679fe69bfe74b7919323515e9ecba9d01422b16be9341b57f88e695b2bb0bd7966b781001d2b9e00ee618fdc239c96e17e32cb379f13f12d6bd7b1b47ad04",
-            "c37bf24c063f0310eccab8864e48dbeffcdd7240b4f8d1e01eba4fc703e6c9082b845bb55543b10f008dc7f4e78540411912ac1f36fa2aa90011dca40f323b09",
-            "3f675d6c364e06405d4868c904e40f3d81c32b083d91586db814d4cb4bf536b4ba209d82f11b4cb6da293b60b20d6122fc3e0e08e80c381dee83edd848211900"
-          ]
-        }
-      }
-    },
-    {
-      "title": "integer wraparound",
-      "filename": "test027_integer_wraparound.bc",
-      "token": [
-        {
-          "symbols": [],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if true || 10000000000 * 10000000000 != 0;\ncheck if true || 9223372036854775807 + 1 != 0;\ncheck if true || -9223372036854775808 - 1 != 0;\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if true || -9223372036854775808 - 1 != 0",
-                  "check if true || 10000000000 * 10000000000 != 0",
-                  "check if true || 9223372036854775807 + 1 != 0"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Err": {
-              "Execution": "Overflow"
-            }
-          },
-          "authorizer_code": "allow if true;\n",
-          "revocation_ids": [
-            "3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05"
-          ]
-        }
-      }
-    },
-    {
-      "title": "test expression syntax and all available operations (v4 blocks)",
-      "filename": "test028_expressions_v4.bc",
-      "token": [
-        {
-          "symbols": [
-            "abcD12x",
-            "abcD12"
-          ],
-          "public_keys": [],
-          "external_key": null,
-          "code": "check if 1 != 3;\ncheck if 1 | 2 ^ 3 == 0;\ncheck if \"abcD12x\" != \"abcD12\";\ncheck if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z;\ncheck if hex:12abcd != hex:12ab;\ncheck if [1, 4] != [1, 2];\n"
-        }
-      ],
-      "validations": {
-        "": {
-          "world": {
-            "facts": [],
-            "rules": [],
-            "checks": [
-              {
-                "origin": 0,
-                "checks": [
-                  "check if \"abcD12x\" != \"abcD12\"",
-                  "check if 1 != 3",
-                  "check if 1 | 2 ^ 3 == 0",
-                  "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z",
-                  "check if [1, 4] != [1, 2]",
-                  "check if hex:12abcd != hex:12ab"
-                ]
-              }
-            ],
-            "policies": [
-              "allow if true"
-            ]
-          },
-          "result": {
-            "Ok": 0
-          },
-          "authorizer_code": "allow if true;\n",
-          "revocation_ids": [
-            "117fa653744c859561555e6a6f5990e3a8e7817f91b87aa6991b6d64297158b4e884c92d10f49f74c96069df722aa676839b72751ca9d1fe83a7025b591de00b"
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "resource(\"file1\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")",
+                  "right(\"file1\", \"write\")",
+                  "right(\"file2\", \"read\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 1,
+                "checks": [
+                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 1,
+                        "check_id": 0,
+                        "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file1\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "7595a112a1eb5b81a6e398852e6118b7f5b8cbbff452778e655100e5fb4faa8d3a2af52fe2c4f9524879605675fae26adbc4783e0cafc43522fa82385f396c03",
+            "45f4c14f9d9e8fa044d68be7a2ec8cddb835f575c7b913ec59bd636c70acae9a90db9064ba0b3084290ed0c422bbb7170092a884f5e0202b31e9235bbcc1650d"
+          ]
+        }
+      }
+    },
+    {
+      "title": "different root key",
+      "filename": "test002_different_root_key.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": null,
+          "result": {
+            "Err": {
+              "Format": {
+                "Signature": {
+                  "InvalidSignature": "signature error: Verification equation was not satisfied"
+                }
+              }
+            }
+          },
+          "authorizer_code": "",
+          "revocation_ids": []
+        }
+      }
+    },
+    {
+      "title": "invalid signature format",
+      "filename": "test003_invalid_signature_format.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1",
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": null,
+          "result": {
+            "Err": {
+              "Format": {
+                "BlockSignatureDeserializationError": "block signature deserialization error: [117, 149, 161, 18, 161, 235, 91, 129, 166, 227, 152, 133, 46, 97, 24, 183]"
+              }
+            }
+          },
+          "authorizer_code": "",
+          "revocation_ids": []
+        }
+      }
+    },
+    {
+      "title": "random block",
+      "filename": "test004_random_block.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1",
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": null,
+          "result": {
+            "Err": {
+              "Format": {
+                "Signature": {
+                  "InvalidSignature": "signature error: Verification equation was not satisfied"
+                }
+              }
+            }
+          },
+          "authorizer_code": "",
+          "revocation_ids": []
+        }
+      }
+    },
+    {
+      "title": "invalid signature",
+      "filename": "test005_invalid_signature.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1",
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": null,
+          "result": {
+            "Err": {
+              "Format": {
+                "Signature": {
+                  "InvalidSignature": "signature error: Verification equation was not satisfied"
+                }
+              }
+            }
+          },
+          "authorizer_code": "",
+          "revocation_ids": []
+        }
+      }
+    },
+    {
+      "title": "reordered blocks",
+      "filename": "test006_reordered_blocks.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1",
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource(\"file1\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": null,
+          "result": {
+            "Err": {
+              "Format": {
+                "Signature": {
+                  "InvalidSignature": "signature error: Verification equation was not satisfied"
+                }
+              }
+            }
+          },
+          "authorizer_code": "",
+          "revocation_ids": []
+        }
+      }
+    },
+    {
+      "title": "scoped rules",
+      "filename": "test007_scoped_rules.bc",
+      "token": [
+        {
+          "symbols": [
+            "user_id",
+            "alice",
+            "file1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "user_id(\"alice\");\nowner(\"alice\", \"file1\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0",
+            "1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "owner(\"alice\", \"file2\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file2\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "owner(\"alice\", \"file1\")",
+                  "user_id(\"alice\")"
+                ]
+              },
+              {
+                "origin": [
+                  2
+                ],
+                "facts": [
+                  "owner(\"alice\", \"file2\")"
+                ]
+              }
+            ],
+            "rules": [
+              {
+                "origin": 1,
+                "rules": [
+                  "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0)"
+                ]
+              }
+            ],
+            "checks": [
+              {
+                "origin": 1,
+                "checks": [
+                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 1,
+                        "check_id": 0,
+                        "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "4d86c9af808dc2e0583f47282e6f5df3e09dc264d5231ec360b4519e15ddaeec60b25a9bbcb22e8d192f4d36a0da3f9243711e30535b00ee55c53cb1395f230a",
+            "63208c668c66f3ba6927140ba37533593b25e03459447805d4b2a8b75adeef45794c3d7249afe506ed77ccee276160bb4052a4009302bd34871a440f070b4509",
+            "d8da982888eae8c038e4894a8c06fc57d8e5f06ad2e972b9cf4bde49ad60804558a0d1938192596c702d8e4f7f12ec19201d7c33d0cd77774a0d879a33880d02"
+          ]
+        }
+      }
+    },
+    {
+      "title": "scoped checks",
+      "filename": "test008_scoped_checks.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file2\", \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file2\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")"
+                ]
+              },
+              {
+                "origin": [
+                  2
+                ],
+                "facts": [
+                  "right(\"file2\", \"read\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 1,
+                "checks": [
+                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 1,
+                        "check_id": 0,
+                        "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00",
+            "77df45442be86a416aa02fd9d98d6d4703c634a9e3b1d293b41f5dc97849afbe7faeec8c22a210574888acc008fb64fe691ec9e8d2655586f970d9a6b6577000",
+            "b31398aefe97d3db41ebc445760f216fb3aa7bf7439adcfc3a07489bfcc163970af3f4e20f5460aa24cf841101a5ab114d21acc0ee8d442bae7793b121284900"
+          ]
+        }
+      }
+    },
+    {
+      "title": "expired token",
+      "filename": "test009_expired_token.bc",
+      "token": [
+        {
+          "symbols": [],
+          "public_keys": [],
+          "external_key": null,
+          "code": "",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "file1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource(\"file1\");\ncheck if time($time), $time <= 2018-12-20T00:00:00Z;\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file1\")",
+                  "time(2020-12-21T09:23:12Z)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 1,
+                "checks": [
+                  "check if resource(\"file1\")",
+                  "check if time($time), $time <= 2018-12-20T00:00:00Z"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 1,
+                        "check_id": 1,
+                        "rule": "check if time($time), $time <= 2018-12-20T00:00:00Z"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file1\");\noperation(\"read\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",
+          "revocation_ids": [
+            "c248907bb6e5f433bbb5edf6367b399ebefca0d321d0b2ea9fc67f66dc1064ce926adb0c05d90c3e8a2833328b3578f79c4e1bca43583d9bcfb2ba6c37303d00",
+            "a4edf7aaea8658bb9ae19b3ffe2adcc77cc9f16c249aeb0a85a584b5362f89f27f7c67ac0af16d7170673d6d1fb1563d1934b25ec5a461f6c01fa49805cd5e07"
+          ]
+        }
+      }
+    },
+    {
+      "title": "authorizer scope",
+      "filename": "test010_authorizer_scope.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file2\", \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file2\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")"
+                ]
+              },
+              {
+                "origin": [
+                  1
+                ],
+                "facts": [
+                  "right(\"file2\", \"read\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 18446744073709551615,
+                "checks": [
+                  "check if right($0, $1), resource($0), operation($1)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Authorizer": {
+                        "check_id": 0,
+                        "rule": "check if right($0, $1), resource($0), operation($1)"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\ncheck if right($0, $1), resource($0), operation($1);\n\nallow if true;\n",
+          "revocation_ids": [
+            "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00",
+            "966eceb2aa937c41b25368808bab6e0698c02a4038de669d007c9c3d43602638a640083558d1576ac80cf3eb2ac6a7585527e0f6c1a65402f0935cf7f4df8005"
+          ]
+        }
+      }
+    },
+    {
+      "title": "authorizer authority checks",
+      "filename": "test011_authorizer_authority_caveats.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file2\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 18446744073709551615,
+                "checks": [
+                  "check if right($0, $1), resource($0), operation($1)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Authorizer": {
+                        "check_id": 0,
+                        "rule": "check if right($0, $1), resource($0), operation($1)"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\ncheck if right($0, $1), resource($0), operation($1);\n\nallow if true;\n",
+          "revocation_ids": [
+            "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00"
+          ]
+        }
+      }
+    },
+    {
+      "title": "authority checks",
+      "filename": "test012_authority_caveats.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource(\"file1\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "file1": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file1\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if resource(\"file1\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "6a8f90dad67ae2ac188460463914ae7326fda431c80785755f4edcc15f1a53911f7366e606ad80cbbeba94672e42713e88632a932128f1d796ce9ba7d7a0b80a"
+          ]
+        },
+        "file2": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file2\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if resource(\"file1\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check if resource(\"file1\")"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "6a8f90dad67ae2ac188460463914ae7326fda431c80785755f4edcc15f1a53911f7366e606ad80cbbeba94672e42713e88632a932128f1d796ce9ba7d7a0b80a"
+          ]
+        }
+      }
+    },
+    {
+      "title": "block rules",
+      "filename": "test013_block_rules.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1",
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "valid_date",
+            "0",
+            "1"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z;\nvalid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1);\ncheck if valid_date($0), resource($0);\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "file1": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "resource(\"file1\")",
+                  "time(2020-12-21T09:23:12Z)"
+                ]
+              },
+              {
+                "origin": [
+                  null,
+                  1
+                ],
+                "facts": [
+                  "valid_date(\"file1\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")",
+                  "right(\"file2\", \"read\")"
+                ]
+              }
+            ],
+            "rules": [
+              {
+                "origin": 1,
+                "rules": [
+                  "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z",
+                  "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)"
+                ]
+              }
+            ],
+            "checks": [
+              {
+                "origin": 1,
+                "checks": [
+                  "check if valid_date($0), resource($0)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "resource(\"file1\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",
+          "revocation_ids": [
+            "c46d071ff3f33434223c8305fdad529f62bf78bb5d9cbfc2a345d4bca6bf314014840e18ba353f86fdb9073d58b12b8c872ac1f8e593c2e9064b90f6c2ede006",
+            "a0c4c163a0b3ca406df4ece3d1371356190df04208eccef72f77e875ed0531b5d37e243d6f388b1967776a5dfd16ef228f19c5bdd6d2820f145c5ed3c3dcdc00"
+          ]
+        },
+        "file2": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "resource(\"file2\")",
+                  "time(2020-12-21T09:23:12Z)"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")",
+                  "right(\"file2\", \"read\")"
+                ]
+              }
+            ],
+            "rules": [
+              {
+                "origin": 1,
+                "rules": [
+                  "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z",
+                  "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)"
+                ]
+              }
+            ],
+            "checks": [
+              {
+                "origin": 1,
+                "checks": [
+                  "check if valid_date($0), resource($0)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 1,
+                        "check_id": 0,
+                        "rule": "check if valid_date($0), resource($0)"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file2\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",
+          "revocation_ids": [
+            "c46d071ff3f33434223c8305fdad529f62bf78bb5d9cbfc2a345d4bca6bf314014840e18ba353f86fdb9073d58b12b8c872ac1f8e593c2e9064b90f6c2ede006",
+            "a0c4c163a0b3ca406df4ece3d1371356190df04208eccef72f77e875ed0531b5d37e243d6f388b1967776a5dfd16ef228f19c5bdd6d2820f145c5ed3c3dcdc00"
+          ]
+        }
+      }
+    },
+    {
+      "title": "regex_constraint",
+      "filename": "test014_regex_constraint.bc",
+      "token": [
+        {
+          "symbols": [
+            "0",
+            "file[0-9]+.txt"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), $0.matches(\"file[0-9]+.txt\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "file1": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "resource(\"file1\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if resource($0), $0.matches(\"file[0-9]+.txt\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check if resource($0), $0.matches(\"file[0-9]+.txt\")"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "resource(\"file1\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "da42718ad2631c12d3a44b7710dcc76c6c7809c6bc3a2d7eb0378c4154eae10e0884a8d54a2cd25ca3dfe01091d816ebbb9d246227baf7a359a787cb2344ad07"
+          ]
+        },
+        "file123": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "resource(\"file123.txt\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if resource($0), $0.matches(\"file[0-9]+.txt\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "resource(\"file123.txt\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "da42718ad2631c12d3a44b7710dcc76c6c7809c6bc3a2d7eb0378c4154eae10e0884a8d54a2cd25ca3dfe01091d816ebbb9d246227baf7a359a787cb2344ad07"
+          ]
+        }
+      }
+    },
+    {
+      "title": "multi queries checks",
+      "filename": "test015_multi_queries_caveats.bc",
+      "token": [
+        {
+          "symbols": [
+            "must_be_present",
+            "hello"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "must_be_present(\"hello\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "must_be_present(\"hello\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 18446744073709551615,
+                "checks": [
+                  "check if must_be_present($0) or must_be_present($0)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "check if must_be_present($0) or must_be_present($0);\n\nallow if true;\n",
+          "revocation_ids": [
+            "b0d466d31e015fa85a075fa875f7e1c9017edd503fee9f62a5f033e1fcfa811074b6e39dfe5af2f452043db97a3f98650592a370f5685b62c5d6abf9dd10b603"
+          ]
+        }
+      }
+    },
+    {
+      "title": "check head name should be independent from fact names",
+      "filename": "test016_caveat_head_name.bc",
+      "token": [
+        {
+          "symbols": [
+            "hello"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource(\"hello\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "test"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "query(\"test\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  1
+                ],
+                "facts": [
+                  "query(\"test\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if resource(\"hello\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check if resource(\"hello\")"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "ce6f804f4390e693a8853d9a4a10bd4f3c94b86b7c6d671993a6e19346bc4d20bbb52cc945e5d0d02e4e75fa5da2caa99764050190353564a0a0b4b276809402",
+            "916d566cc724e0773046fc5266e9d0d804311435b8d6955b332f823ab296be9a78dfea190447732ac9f6217234cf5726becf88f65169c6de56a766af55451b0f"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test expression syntax and all available operations",
+      "filename": "test017_expressions.bc",
+      "token": [
+        {
+          "symbols": [
+            "hello world",
+            "hello",
+            "world",
+            "aaabde",
+            "a*c?.e",
+            "abd",
+            "aaa",
+            "b",
+            "de",
+            "abcD12",
+            "é",
+            "abc",
+            "def"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if true;\ncheck if !false;\ncheck if true === true;\ncheck if false === false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 === 3;\ncheck if 1 + 2 * 3 - 4 / 2 === 5;\ncheck if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" === \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" === \"abcD12\";\ncheck if \"abcD12\".length() === 6;\ncheck if \"é\".length() === 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z;\ncheck if hex:12ab === hex:12ab;\ncheck if {1, 2}.contains(2);\ncheck if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z);\ncheck if {false, true}.contains(true);\ncheck if {\"abc\", \"def\"}.contains(\"abc\");\ncheck if {hex:12ab, hex:34de}.contains(hex:34de);\ncheck if {1, 2}.contains({2});\ncheck if {1, 2} === {1, 2};\ncheck if {1, 2}.intersection({2, 3}) === {2};\ncheck if {1, 2}.union({2, 3}) === {1, 2, 3};\ncheck if {1, 2, 3}.intersection({1, 2}).contains(1);\ncheck if {1, 2, 3}.intersection({1, 2}).length() === 2;\ncheck if {,}.length() === 0;\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if !false",
+                  "check if \"aaabde\" === \"aaa\" + \"b\" + \"de\"",
+                  "check if \"aaabde\".contains(\"abd\")",
+                  "check if \"aaabde\".matches(\"a*c?.e\")",
+                  "check if \"abcD12\" === \"abcD12\"",
+                  "check if \"abcD12\".length() === 6",
+                  "check if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\")",
+                  "check if \"é\".length() === 2",
+                  "check if 1 + 2 * 3 - 4 / 2 === 5",
+                  "check if 1 < 2",
+                  "check if 1 <= 1",
+                  "check if 1 <= 2",
+                  "check if 2 > 1",
+                  "check if 2 >= 1",
+                  "check if 2 >= 2",
+                  "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z",
+                  "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z",
+                  "check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z",
+                  "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z",
+                  "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z",
+                  "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z",
+                  "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z",
+                  "check if 3 === 3",
+                  "check if false === false",
+                  "check if hex:12ab === hex:12ab",
+                  "check if true",
+                  "check if true === true",
+                  "check if {\"abc\", \"def\"}.contains(\"abc\")",
+                  "check if {,}.length() === 0",
+                  "check if {1, 2, 3}.intersection({1, 2}).contains(1)",
+                  "check if {1, 2, 3}.intersection({1, 2}).length() === 2",
+                  "check if {1, 2} === {1, 2}",
+                  "check if {1, 2}.contains(2)",
+                  "check if {1, 2}.contains({2})",
+                  "check if {1, 2}.intersection({2, 3}) === {2}",
+                  "check if {1, 2}.union({2, 3}) === {1, 2, 3}",
+                  "check if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z)",
+                  "check if {false, true}.contains(true)",
+                  "check if {hex:12ab, hex:34de}.contains(hex:34de)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505"
+          ]
+        }
+      }
+    },
+    {
+      "title": "invalid block rule with unbound_variables",
+      "filename": "test018_unbound_variables_in_rule.bc",
+      "token": [
+        {
+          "symbols": [],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if operation(\"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "unbound",
+            "any1",
+            "any2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "operation($unbound, \"read\") <- operation($any1, $any2);\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": null,
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "InvalidBlockRule": [
+                  0,
+                  "operation($unbound, \"read\") <- operation($any1, $any2)"
+                ]
+              }
+            }
+          },
+          "authorizer_code": "",
+          "revocation_ids": [
+            "a44210c6a01e55eadefc7d8540c2e6eff80ab6eeedde4751de734f9d780435780680d3f42d826b7e0f0dcf4a5ba303fd4c116984bb30978813d46ed867924307",
+            "b0a33e3f4cd0994c0766c196c4d11c15e5a0f9bfba79a3a2b35ddd04ddb890282a7c63336ada5c680b9f9c940c1fa7127d2699754cbc77c21e1a2d85c5ef700c"
+          ]
+        }
+      }
+    },
+    {
+      "title": "invalid block rule generating an #authority or #ambient symbol with a variable",
+      "filename": "test019_generating_ambient_from_variables.bc",
+      "token": [
+        {
+          "symbols": [],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if operation(\"read\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "any"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "operation(\"read\") <- operation($any);\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"write\")"
+                ]
+              },
+              {
+                "origin": [
+                  null,
+                  1
+                ],
+                "facts": [
+                  "operation(\"read\")"
+                ]
+              }
+            ],
+            "rules": [
+              {
+                "origin": 1,
+                "rules": [
+                  "operation(\"read\") <- operation($any)"
+                ]
+              }
+            ],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if operation(\"read\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check if operation(\"read\")"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "operation(\"write\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "a44210c6a01e55eadefc7d8540c2e6eff80ab6eeedde4751de734f9d780435780680d3f42d826b7e0f0dcf4a5ba303fd4c116984bb30978813d46ed867924307",
+            "d3f8822a9b9bc0ee3933283c493ca9e711be5dd8339b5fe2eba1de3805aad4e84d3e2fb4affb4a743f1289915c167582b9425343635e45b70573ea1ee7a1ea03"
+          ]
+        }
+      }
+    },
+    {
+      "title": "sealed token",
+      "filename": "test020_sealed.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1",
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file1\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")",
+                  "right(\"file1\", \"write\")",
+                  "right(\"file2\", \"read\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 1,
+                "checks": [
+                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "7595a112a1eb5b81a6e398852e6118b7f5b8cbbff452778e655100e5fb4faa8d3a2af52fe2c4f9524879605675fae26adbc4783e0cafc43522fa82385f396c03",
+            "45f4c14f9d9e8fa044d68be7a2ec8cddb835f575c7b913ec59bd636c70acae9a90db9064ba0b3084290ed0c422bbb7170092a884f5e0202b31e9235bbcc1650d"
+          ]
+        }
+      }
+    },
+    {
+      "title": "parsing",
+      "filename": "test021_parsing.bc",
+      "token": [
+        {
+          "symbols": [
+            "ns::fact_123",
+            "hello é\t😁"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "ns::fact_123(\"hello é\t😁\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "ns::fact_123(\"hello é\t😁\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 18446744073709551615,
+                "checks": [
+                  "check if ns::fact_123(\"hello é\t😁\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "check if ns::fact_123(\"hello é\t😁\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "d4b2f417b6e906434fdf5058afcabfcb98d3628f814f1c9dd7e64250d9beec4465aff51bd0cb2e85d0e67dc9f613c2a42af6158c678bc6f8b4684cd3a2d0d302"
+          ]
+        }
+      }
+    },
+    {
+      "title": "default_symbols",
+      "filename": "test022_default_symbols.bc",
+      "token": [
+        {
+          "symbols": [],
+          "public_keys": [],
+          "external_key": null,
+          "code": "read(0);\nwrite(1);\nresource(2);\noperation(3);\nright(4);\ntime(5);\nrole(6);\nowner(7);\ntenant(8);\nnamespace(9);\nuser(10);\nteam(11);\nservice(12);\nadmin(13);\nemail(14);\ngroup(15);\nmember(16);\nip_address(17);\nclient(18);\nclient_ip(19);\ndomain(20);\npath(21);\nversion(22);\ncluster(23);\nnode(24);\nhostname(25);\nnonce(26);\nquery(27);\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "admin(13)",
+                  "client(18)",
+                  "client_ip(19)",
+                  "cluster(23)",
+                  "domain(20)",
+                  "email(14)",
+                  "group(15)",
+                  "hostname(25)",
+                  "ip_address(17)",
+                  "member(16)",
+                  "namespace(9)",
+                  "node(24)",
+                  "nonce(26)",
+                  "operation(3)",
+                  "owner(7)",
+                  "path(21)",
+                  "query(27)",
+                  "read(0)",
+                  "resource(2)",
+                  "right(4)",
+                  "role(6)",
+                  "service(12)",
+                  "team(11)",
+                  "tenant(8)",
+                  "time(5)",
+                  "user(10)",
+                  "version(22)",
+                  "write(1)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 18446744073709551615,
+                "checks": [
+                  "check if read(0), write(1), resource(2), operation(3), right(4), time(5), role(6), owner(7), tenant(8), namespace(9), user(10), team(11), service(12), admin(13), email(14), group(15), member(16), ip_address(17), client(18), client_ip(19), domain(20), path(21), version(22), cluster(23), node(24), hostname(25), nonce(26), query(27)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "check if read(0), write(1), resource(2), operation(3), right(4), time(5), role(6), owner(7), tenant(8), namespace(9), user(10), team(11), service(12), admin(13), email(14), group(15), member(16), ip_address(17), client(18), client_ip(19), domain(20), path(21), version(22), cluster(23), node(24), hostname(25), nonce(26), query(27);\n\nallow if true;\n",
+          "revocation_ids": [
+            "75ce48d496fd28f99905901783a1ba46d7ff8d69f9d364d1546fd73006026eae51849ad1190a4ae521a0a1269f9c6951e226afba8fcd24fa50f679162439ae09"
+          ]
+        }
+      }
+    },
+    {
+      "title": "execution scope",
+      "filename": "test023_execution_scope.bc",
+      "token": [
+        {
+          "symbols": [
+            "authority_fact"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "authority_fact(1);\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "block1_fact"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "block1_fact(1);\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "var"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if authority_fact($var);\ncheck if block1_fact($var);\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "authority_fact(1)"
+                ]
+              },
+              {
+                "origin": [
+                  1
+                ],
+                "facts": [
+                  "block1_fact(1)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 2,
+                "checks": [
+                  "check if authority_fact($var)",
+                  "check if block1_fact($var)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 2,
+                        "check_id": 1,
+                        "rule": "check if block1_fact($var)"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "f9b49866caef5ece7be14ec5a9b36d98ca81d06b306eb0b4c57cd7436af176f40ee972f40903f87ec4460ab8b1adfcbfa9b19b20a6955a1e8dae7d88b2076005",
+            "889054b9119e4440e54da1b63266a98d0f6646cde195fef206efd8b133cfb2ee7be49b32a9a5925ece452e64f9e6f6d80dab422e916c599675dd68cdea053802",
+            "0a85ffbf27e08aa23665ba0d96a985b274d747556c9f016fd7f590c641ed0e4133291521aa442b320ee9ce80f5ad701b914a0c87b3dfa0cc92629dce94201806"
+          ]
+        }
+      }
+    },
+    {
+      "title": "third party",
+      "filename": "test024_third_party.bc",
+      "token": [
+        {
+          "symbols": [],
+          "public_keys": [
+            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
+          ],
+          "external_key": null,
+          "code": "right(\"read\");\ncheck if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n",
+          "version": 4
+        },
+        {
+          "symbols": [],
+          "public_keys": [],
+          "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
+          "code": "group(\"admin\");\ncheck if right(\"read\");\n",
+          "version": 5
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"read\")"
+                ]
+              },
+              {
+                "origin": [
+                  1
+                ],
+                "facts": [
+                  "group(\"admin\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
+                ]
+              },
+              {
+                "origin": 1,
+                "checks": [
+                  "check if right(\"read\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "470e4bf7aa2a01ab39c98150bd06aa15b4aa5d86509044a8809a8634cd8cf2b42269a51a774b65d10bac9369d013070b00187925196a8e680108473f11cf8f03",
+            "901b2af4dacf33458d2d91ac484b60bad948e8d10faa9695b096054d5b46e832a977b60b17464cacf545ad0801f549ea454675f0ac88c413406925e2af83ff08"
+          ]
+        }
+      }
+    },
+    {
+      "title": "block rules",
+      "filename": "test025_check_all.bc",
+      "token": [
+        {
+          "symbols": [
+            "allowed_operations",
+            "A",
+            "B",
+            "op",
+            "allowed"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "allowed_operations({\"A\", \"B\"});\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n",
+          "version": 4
+        }
+      ],
+      "validations": {
+        "A, B": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"A\")",
+                  "operation(\"B\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "allowed_operations({\"A\", \"B\"})"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "operation(\"A\");\noperation(\"B\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"
+          ]
+        },
+        "A, invalid": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"A\")",
+                  "operation(\"invalid\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "allowed_operations({\"A\", \"B\"})"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "operation(\"A\");\noperation(\"invalid\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"
+          ]
+        },
+        "no matches": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "allowed_operations({\"A\", \"B\"})"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"
+          ]
+        }
+      }
+    },
+    {
+      "title": "public keys interning",
+      "filename": "test026_public_keys_interning.bc",
+      "token": [
+        {
+          "symbols": [],
+          "public_keys": [
+            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
+          ],
+          "external_key": null,
+          "code": "query(0);\ncheck if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n",
+          "version": 4
+        },
+        {
+          "symbols": [],
+          "public_keys": [
+            "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
+            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
+          ],
+          "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
+          "code": "query(1);\nquery(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n",
+          "version": 5
+        },
+        {
+          "symbols": [],
+          "public_keys": [
+            "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
+            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
+          ],
+          "external_key": "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
+          "code": "query(2);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n",
+          "version": 5
+        },
+        {
+          "symbols": [],
+          "public_keys": [
+            "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
+            "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
+          ],
+          "external_key": "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
+          "code": "query(3);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n",
+          "version": 5
+        },
+        {
+          "symbols": [],
+          "public_keys": [
+            "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
+            "ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"
+          ],
+          "external_key": null,
+          "code": "query(4);\ncheck if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136;\n",
+          "version": 4
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "query(0)"
+                ]
+              },
+              {
+                "origin": [
+                  1
+                ],
+                "facts": [
+                  "query(1)"
+                ]
+              },
+              {
+                "origin": [
+                  1,
+                  2
+                ],
+                "facts": [
+                  "query(1, 2)"
+                ]
+              },
+              {
+                "origin": [
+                  2
+                ],
+                "facts": [
+                  "query(2)"
+                ]
+              },
+              {
+                "origin": [
+                  3
+                ],
+                "facts": [
+                  "query(3)"
+                ]
+              },
+              {
+                "origin": [
+                  4
+                ],
+                "facts": [
+                  "query(4)"
+                ]
+              }
+            ],
+            "rules": [
+              {
+                "origin": 1,
+                "rules": [
+                  "query(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
+                ]
+              }
+            ],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"
+                ]
+              },
+              {
+                "origin": 1,
+                "checks": [
+                  "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
+                  "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
+                ]
+              },
+              {
+                "origin": 2,
+                "checks": [
+                  "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
+                  "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
+                ]
+              },
+              {
+                "origin": 3,
+                "checks": [
+                  "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
+                  "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
+                ]
+              },
+              {
+                "origin": 4,
+                "checks": [
+                  "check if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",
+                  "check if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"
+                ]
+              },
+              {
+                "origin": 18446744073709551615,
+                "checks": [
+                  "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"
+                ]
+              }
+            ],
+            "policies": [
+              "deny if query(3)",
+              "deny if query(1, 2)",
+              "deny if query(0) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 3
+          },
+          "authorizer_code": "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\n\ndeny if query(3);\ndeny if query(1, 2);\ndeny if query(0) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\nallow if true;\n",
+          "revocation_ids": [
+            "3771cefe71beb21ead35a59c8116ee82627a5717c0295f35980662abccb159fe1b37848cb1818e548656bd4fd882d0094a2daab631c76b2b72e3a093914bfe04",
+            "7113d4dbb3b688b80e941f365a2c6342d480c77ed03937bccf85dc5cc3554c7517887b1b0c9021388a71e6ca9047aabaaad5ae5b511a2880902568444a98e50b",
+            "d0e3fc4bbd1b7320022800af909585aa906f677c4ca79c275a10b6779f669384c464ee84a1b04f13877a25761a874748362c065f4d15a8cab5c5e16c34074403",
+            "29b7e0a1f118a6185814a552660c516c43482044e280e7a8de85b8e7e54947e0ae82eb39d7b524d4b72cb9812a7a4b8871964f8f825b1c1ed85d344c05281d0d",
+            "c0a505d4d921a8b2d0b885917d42e2bca87b5302d13249a61af6f3802af44d691c40a624f901d677724740cb974a188aeb1c3992c1565ac0fbec3aa4f68dac0a"
+          ]
+        }
+      }
+    },
+    {
+      "title": "integer wraparound",
+      "filename": "test027_integer_wraparound.bc",
+      "token": [
+        {
+          "symbols": [],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if 10000000000 * 10000000000 !== 0;\ncheck if 9223372036854775807 + 1 !== 0;\ncheck if -9223372036854775808 - 1 !== 0;\n",
+          "version": 4
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if -9223372036854775808 - 1 !== 0",
+                  "check if 10000000000 * 10000000000 !== 0",
+                  "check if 9223372036854775807 + 1 !== 0"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "Execution": "Overflow"
+            }
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test expression syntax and all available operations (v4 blocks)",
+      "filename": "test028_expressions_v4.bc",
+      "token": [
+        {
+          "symbols": [
+            "abcD12x",
+            "abcD12"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if true !== false;\ncheck if 1 !== 3;\ncheck if 1 | 2 ^ 3 === 0;\ncheck if \"abcD12x\" !== \"abcD12\";\ncheck if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z;\ncheck if hex:12abcd !== hex:12ab;\ncheck if {1, 4} !== {1, 2};\n",
+          "version": 4
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if \"abcD12x\" !== \"abcD12\"",
+                  "check if 1 !== 3",
+                  "check if 1 | 2 ^ 3 === 0",
+                  "check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z",
+                  "check if hex:12abcd !== hex:12ab",
+                  "check if true !== false",
+                  "check if {1, 4} !== {1, 2}"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "9402c07923aa33bc911de80e61f388f5c4533e6b36e45317dc1db1e6bcc7664ed0c1c504d0ca8925208008961d95bbdbc36f6e3d91b3173369cc19ed625e9a0c"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test reject if",
+      "filename": "test029_reject_if.bc",
+      "token": [
+        {
+          "symbols": [
+            "test"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "reject if test($test), $test;\n",
+          "version": 6
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "test(false)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "reject if test($test), $test"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "test(false);\n\nallow if true;\n",
+          "revocation_ids": [
+            "8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a"
+          ]
+        },
+        "rejection": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "test(true)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "reject if test($test), $test"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "reject if test($test), $test"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "test(true);\n\nallow if true;\n",
+          "revocation_ids": [
+            "8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test null",
+      "filename": "test030_null.bc",
+      "token": [
+        {
+          "symbols": [
+            "fact",
+            "value"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if fact(null, $value), $value == null;\nreject if fact(null, $value), $value != null;\n",
+          "version": 6
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "fact(null, null)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if fact(null, $value), $value == null",
+                  "reject if fact(null, $value), $value != null"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "fact(null, null);\n\nallow if true;\n",
+          "revocation_ids": [
+            "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b"
+          ]
+        },
+        "rejection1": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "fact(null, 1)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if fact(null, $value), $value == null",
+                  "reject if fact(null, $value), $value != null"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check if fact(null, $value), $value == null"
+                      }
+                    },
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 1,
+                        "rule": "reject if fact(null, $value), $value != null"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "fact(null, 1);\n\nallow if true;\n",
+          "revocation_ids": [
+            "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b"
+          ]
+        },
+        "rejection2": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "fact(null, true)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if fact(null, $value), $value == null",
+                  "reject if fact(null, $value), $value != null"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check if fact(null, $value), $value == null"
+                      }
+                    },
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 1,
+                        "rule": "reject if fact(null, $value), $value != null"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "fact(null, true);\n\nallow if true;\n",
+          "revocation_ids": [
+            "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b"
+          ]
+        },
+        "rejection3": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "fact(null, \"abcd\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if fact(null, $value), $value == null",
+                  "reject if fact(null, $value), $value != null"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 0,
+                        "rule": "check if fact(null, $value), $value == null"
+                      }
+                    },
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 1,
+                        "rule": "reject if fact(null, $value), $value != null"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "fact(null, \"abcd\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test heterogeneous equal",
+      "filename": "test031_heterogeneous_equal.bc",
+      "token": [
+        {
+          "symbols": [
+            "abcD12",
+            "abcD12x",
+            "fact",
+            "value",
+            "fact2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if true == true;\ncheck if false == false;\ncheck if false != true;\ncheck if 1 != true;\ncheck if 1 == 1;\ncheck if 1 != 3;\ncheck if 1 != true;\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12x\" != \"abcD12\";\ncheck if \"abcD12x\" != true;\ncheck if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z;\ncheck if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z;\ncheck if 2022-12-04T09:46:41Z != true;\ncheck if hex:12abcd == hex:12abcd;\ncheck if hex:12abcd != hex:12ab;\ncheck if hex:12abcd != true;\ncheck if {1, 2} == {1, 2};\ncheck if {1, 4} != {1, 2};\ncheck if {1, 4} != true;\ncheck if fact(1, $value), 1 == $value;\ncheck if fact2(1, $value), 1 != $value;\n",
+          "version": 6
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "fact(1, 1)",
+                  "fact2(1, 2)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if \"abcD12\" == \"abcD12\"",
+                  "check if \"abcD12x\" != \"abcD12\"",
+                  "check if \"abcD12x\" != true",
+                  "check if 1 != 3",
+                  "check if 1 != true",
+                  "check if 1 != true",
+                  "check if 1 == 1",
+                  "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z",
+                  "check if 2022-12-04T09:46:41Z != true",
+                  "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z",
+                  "check if fact(1, $value), 1 == $value",
+                  "check if fact2(1, $value), 1 != $value",
+                  "check if false != true",
+                  "check if false == false",
+                  "check if hex:12abcd != hex:12ab",
+                  "check if hex:12abcd != true",
+                  "check if hex:12abcd == hex:12abcd",
+                  "check if true == true",
+                  "check if {1, 2} == {1, 2}",
+                  "check if {1, 4} != true",
+                  "check if {1, 4} != {1, 2}"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "fact(1, 1);\nfact2(1, 2);\n\nallow if true;\n",
+          "revocation_ids": [
+            "be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a"
+          ]
+        },
+        "evaluate to false": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "fact(1, 2)",
+                  "fact2(1, 1)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if \"abcD12\" == \"abcD12\"",
+                  "check if \"abcD12x\" != \"abcD12\"",
+                  "check if \"abcD12x\" != true",
+                  "check if 1 != 3",
+                  "check if 1 != true",
+                  "check if 1 != true",
+                  "check if 1 == 1",
+                  "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z",
+                  "check if 2022-12-04T09:46:41Z != true",
+                  "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z",
+                  "check if fact(1, $value), 1 == $value",
+                  "check if fact2(1, $value), 1 != $value",
+                  "check if false != true",
+                  "check if false == false",
+                  "check if hex:12abcd != hex:12ab",
+                  "check if hex:12abcd != true",
+                  "check if hex:12abcd == hex:12abcd",
+                  "check if true == true",
+                  "check if {1, 2} == {1, 2}",
+                  "check if {1, 4} != true",
+                  "check if {1, 4} != {1, 2}"
+                ]
+              },
+              {
+                "origin": 18446744073709551615,
+                "checks": [
+                  "check if false != false"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "FailedLogic": {
+                "Unauthorized": {
+                  "policy": {
+                    "Allow": 0
+                  },
+                  "checks": [
+                    {
+                      "Authorizer": {
+                        "check_id": 0,
+                        "rule": "check if false != false"
+                      }
+                    },
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 19,
+                        "rule": "check if fact(1, $value), 1 == $value"
+                      }
+                    },
+                    {
+                      "Block": {
+                        "block_id": 0,
+                        "check_id": 20,
+                        "rule": "check if fact2(1, $value), 1 != $value"
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "authorizer_code": "fact(1, 2);\nfact2(1, 1);\n\ncheck if false != false;\n\nallow if true;\n",
+          "revocation_ids": [
+            "be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test laziness and closures",
+      "filename": "test032_laziness_closures.bc",
+      "token": [
+        {
+          "symbols": [
+            "x",
+            "p",
+            "q"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if !(false && \"x\".intersection(\"x\"));\ncheck if true || \"x\".intersection(\"x\");\ncheck if {1, 2, 3}.all($p -> $p > 0);\ncheck if !{1, 2, 3}.all($p -> $p == 2);\ncheck if {1, 2, 3}.any($p -> $p > 2);\ncheck if !{1, 2, 3}.any($p -> $p > 3);\ncheck if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q));\n",
+          "version": 6
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if !(false && \"x\".intersection(\"x\"))",
+                  "check if !false && true",
+                  "check if !{1, 2, 3}.all($p -> $p == 2)",
+                  "check if !{1, 2, 3}.any($p -> $p > 3)",
+                  "check if (true || false) && true",
+                  "check if false || true",
+                  "check if true || \"x\".intersection(\"x\")",
+                  "check if {1, 2, 3}.all($p -> $p > 0)",
+                  "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))",
+                  "check if {1, 2, 3}.any($p -> $p > 2)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02"
+          ]
+        },
+        "shadowing": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if !(false && \"x\".intersection(\"x\"))",
+                  "check if !false && true",
+                  "check if !{1, 2, 3}.all($p -> $p == 2)",
+                  "check if !{1, 2, 3}.any($p -> $p > 3)",
+                  "check if (true || false) && true",
+                  "check if false || true",
+                  "check if true || \"x\".intersection(\"x\")",
+                  "check if {1, 2, 3}.all($p -> $p > 0)",
+                  "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))",
+                  "check if {1, 2, 3}.any($p -> $p > 2)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if {\"true\"}.any($p -> {\"true\"}.all($p -> $p))"
+            ]
+          },
+          "result": {
+            "Err": {
+              "Execution": "ShadowedVariable"
+            }
+          },
+          "authorizer_code": "allow if {\"true\"}.any($p -> {\"true\"}.all($p -> $p));\n",
+          "revocation_ids": [
+            "2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test .type()",
+      "filename": "test033_typeof.bc",
+      "token": [
+        {
+          "symbols": [
+            "integer",
+            "string",
+            "test",
+            "date",
+            "bytes",
+            "bool",
+            "set",
+            "null",
+            "array",
+            "map",
+            "a",
+            "t"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "integer(1);\nstring(\"test\");\ndate(2023-12-28T00:00:00Z);\nbytes(hex:aa);\nbool(true);\nset({false, true});\nnull(null);\narray([1, 2, 3]);\nmap({\"a\": true});\ncheck if 1.type() == \"integer\";\ncheck if integer($t), $t.type() == \"integer\";\ncheck if \"test\".type() == \"string\";\ncheck if string($t), $t.type() == \"string\";\ncheck if (2023-12-28T00:00:00Z).type() == \"date\";\ncheck if date($t), $t.type() == \"date\";\ncheck if hex:aa.type() == \"bytes\";\ncheck if bytes($t), $t.type() == \"bytes\";\ncheck if true.type() == \"bool\";\ncheck if bool($t), $t.type() == \"bool\";\ncheck if {false, true}.type() == \"set\";\ncheck if set($t), $t.type() == \"set\";\ncheck if null.type() == \"null\";\ncheck if null($t), $t.type() == \"null\";\ncheck if array($t), $t.type() == \"array\";\ncheck if map($t), $t.type() == \"map\";\n",
+          "version": 6
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "array([1, 2, 3])",
+                  "bool(true)",
+                  "bytes(hex:aa)",
+                  "date(2023-12-28T00:00:00Z)",
+                  "integer(1)",
+                  "map({\"a\": true})",
+                  "null(null)",
+                  "set({false, true})",
+                  "string(\"test\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if \"test\".type() == \"string\"",
+                  "check if (2023-12-28T00:00:00Z).type() == \"date\"",
+                  "check if 1.type() == \"integer\"",
+                  "check if array($t), $t.type() == \"array\"",
+                  "check if bool($t), $t.type() == \"bool\"",
+                  "check if bytes($t), $t.type() == \"bytes\"",
+                  "check if date($t), $t.type() == \"date\"",
+                  "check if hex:aa.type() == \"bytes\"",
+                  "check if integer($t), $t.type() == \"integer\"",
+                  "check if map($t), $t.type() == \"map\"",
+                  "check if null($t), $t.type() == \"null\"",
+                  "check if null.type() == \"null\"",
+                  "check if set($t), $t.type() == \"set\"",
+                  "check if string($t), $t.type() == \"string\"",
+                  "check if true.type() == \"bool\"",
+                  "check if {false, true}.type() == \"set\""
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "e60875c6ef7917c227a5e4b2cabfe250a85fa0598eb3cf7987ded0da2b69a559a1665bd312aeecde78e76aeb28ea1c1a03ec9b7dec8aeb519e7867ef8ff9b402"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test array and map operations",
+      "filename": "test034_array_map.bc",
+      "token": [
+        {
+          "symbols": [
+            "a",
+            "b",
+            "c",
+            "p",
+            "d",
+            "A",
+            "kv",
+            "id",
+            "roles"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if [1, 2, 1].length() == 3;\ncheck if [\"a\", \"b\"] != true;\ncheck if [\"a\", \"b\"] != [1, 2, 3];\ncheck if [\"a\", \"b\"] == [\"a\", \"b\"];\ncheck if [\"a\", \"b\"] === [\"a\", \"b\"];\ncheck if [\"a\", \"b\"] !== [\"a\", \"c\"];\ncheck if [\"a\", \"b\", \"c\"].contains(\"c\");\ncheck if [1, 2, 3].starts_with([1, 2]);\ncheck if [4, 5, 6].ends_with([6]);\ncheck if [1, 2, \"a\"].get(2) == \"a\";\ncheck if [1, 2].get(3) == null;\ncheck if [1, 2].get(-1) == null;\ncheck if [1, 2, 3].all($p -> $p > 0);\ncheck if [1, 2, 3].any($p -> $p > 2);\ncheck if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4;\ncheck if {1: \"a\", 2: \"b\"} != true;\ncheck if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2};\ncheck if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"};\ncheck if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2};\ncheck if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"};\ncheck if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\");\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1;\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\";\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null;\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null;\ncheck if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3);\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\");\ncheck if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\");\n",
+          "version": 6
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if [\"a\", \"b\", \"c\"].contains(\"c\")",
+                  "check if [\"a\", \"b\"] != [1, 2, 3]",
+                  "check if [\"a\", \"b\"] != true",
+                  "check if [\"a\", \"b\"] !== [\"a\", \"c\"]",
+                  "check if [\"a\", \"b\"] == [\"a\", \"b\"]",
+                  "check if [\"a\", \"b\"] === [\"a\", \"b\"]",
+                  "check if [1, 2, \"a\"].get(2) == \"a\"",
+                  "check if [1, 2, 1].length() == 3",
+                  "check if [1, 2, 3].all($p -> $p > 0)",
+                  "check if [1, 2, 3].any($p -> $p > 2)",
+                  "check if [1, 2, 3].starts_with([1, 2])",
+                  "check if [1, 2].get(-1) == null",
+                  "check if [1, 2].get(3) == null",
+                  "check if [4, 5, 6].ends_with([6])",
+                  "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\")",
+                  "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4",
+                  "check if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3)",
+                  "check if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\")",
+                  "check if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\")",
+                  "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1",
+                  "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null",
+                  "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\"",
+                  "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null",
+                  "check if {1: \"a\", 2: \"b\"} != true",
+                  "check if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2}",
+                  "check if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2}",
+                  "check if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"}",
+                  "check if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"}"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "0227fc29d6bf85cc52503f497b0b232235ffe5192c68933388136eae3fc3419229e9489b85d1bb57525554edac7cda87c984c96639bfb4171acc499312ffe30e"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test ffi calls (v6 blocks)",
+      "filename": "test035_ffi.bc",
+      "token": [
+        {
+          "symbols": [
+            "test",
+            "a",
+            "equal strings"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if true.extern::test(), \"a\".extern::test(\"a\") == \"equal strings\";\n",
+          "version": 6
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if true.extern::test(), \"a\".extern::test(\"a\") == \"equal strings\""
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "d1719fd101c2695d2dac4df67569918363f691b6167670e1dbbf8026f639a7aa1ec2e13707f4d34cadbb2adce5c6e8a816577dd069a8717e0f5cb4ea3cec5b04"
+          ]
+        }
+      }
+    },
+    {
+      "title": "ECDSA secp256r1 signatures",
+      "filename": "test036_secp256r1.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1",
+            "file2"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n",
+          "version": 3
+        },
+        {
+          "symbols": [
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 3
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file1\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")",
+                  "right(\"file1\", \"write\")",
+                  "right(\"file2\", \"read\")"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 1,
+                "checks": [
+                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d",
+            "3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f"
+          ]
+        }
+      }
+    },
+    {
+      "title": "ECDSA secp256r1 signature on third-party block",
+      "filename": "test037_secp256r1_third_party.bc",
+      "token": [
+        {
+          "symbols": [
+            "file1",
+            "file2",
+            "from_third"
+          ],
+          "public_keys": [
+            "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf"
+          ],
+          "external_key": null,
+          "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\ncheck if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf;\n",
+          "version": 4
+        },
+        {
+          "symbols": [
+            "from_third",
+            "0"
+          ],
+          "public_keys": [],
+          "external_key": "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf",
+          "code": "from_third(true);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n",
+          "version": 5
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [
+              {
+                "origin": [
+                  null
+                ],
+                "facts": [
+                  "operation(\"read\")",
+                  "resource(\"file1\")"
+                ]
+              },
+              {
+                "origin": [
+                  0
+                ],
+                "facts": [
+                  "right(\"file1\", \"read\")",
+                  "right(\"file1\", \"write\")",
+                  "right(\"file2\", \"read\")"
+                ]
+              },
+              {
+                "origin": [
+                  1
+                ],
+                "facts": [
+                  "from_third(true)"
+                ]
+              }
+            ],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf"
+                ]
+              },
+              {
+                "origin": 1,
+                "checks": [
+                  "check if resource($0), operation(\"read\"), right($0, \"read\")"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",
+          "revocation_ids": [
+            "70f5402208516fd44cfc9df3dfcfc0a327ee9004f1801ed0a7abdcbbae923d566ddcd2d4a14f4622b35732c4e538af04075cc67ab0888fa2d8923cc668187f0f",
+            "30450220793f95665d9af646339503a073670ea2c352459d2a2c2e14c57565f6c7eaf6bc022100cccadfc37e46755f52bb054ed206d7335067885df599a69431db40e33f33d4cf"
+          ]
+        }
+      }
+    },
+    {
+      "title": "test try operation",
+      "filename": "test038_try_op.bc",
+      "token": [
+        {
+          "symbols": [],
+          "public_keys": [],
+          "external_key": null,
+          "code": "check if (true === 12).try_or(true);\ncheck if ((true === 12).try_or(true === 12)).try_or(true);\nreject if (true == 12).try_or(true);\n",
+          "version": 6
+        }
+      ],
+      "validations": {
+        "": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if ((true === 12).try_or(true === 12)).try_or(true)",
+                  "check if (true === 12).try_or(true)",
+                  "reject if (true == 12).try_or(true)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Ok": 0
+          },
+          "authorizer_code": "allow if true;\n",
+          "revocation_ids": [
+            "79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600"
+          ]
+        },
+        "right-hand side does not catch errors": {
+          "world": {
+            "facts": [],
+            "rules": [],
+            "checks": [
+              {
+                "origin": 0,
+                "checks": [
+                  "check if ((true === 12).try_or(true === 12)).try_or(true)",
+                  "check if (true === 12).try_or(true)",
+                  "reject if (true == 12).try_or(true)"
+                ]
+              },
+              {
+                "origin": 18446744073709551615,
+                "checks": [
+                  "check if true.try_or(true === 12)"
+                ]
+              }
+            ],
+            "policies": [
+              "allow if true"
+            ]
+          },
+          "result": {
+            "Err": {
+              "Execution": "InvalidType"
+            }
+          },
+          "authorizer_code": "check if true.try_or(true === 12);\n\nallow if true;\n",
+          "revocation_ids": [
+            "79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600"
           ]
         }
       }
diff --git a/test/samples/current/test017_expressions.bc b/test/samples/current/test017_expressions.bc
Binary files a/test/samples/current/test017_expressions.bc and b/test/samples/current/test017_expressions.bc differ
diff --git a/test/samples/current/test024_third_party.bc b/test/samples/current/test024_third_party.bc
Binary files a/test/samples/current/test024_third_party.bc and b/test/samples/current/test024_third_party.bc differ
diff --git a/test/samples/current/test026_public_keys_interning.bc b/test/samples/current/test026_public_keys_interning.bc
Binary files a/test/samples/current/test026_public_keys_interning.bc and b/test/samples/current/test026_public_keys_interning.bc differ
diff --git a/test/samples/current/test027_integer_wraparound.bc b/test/samples/current/test027_integer_wraparound.bc
Binary files a/test/samples/current/test027_integer_wraparound.bc and b/test/samples/current/test027_integer_wraparound.bc differ
diff --git a/test/samples/current/test028_expressions_v4.bc b/test/samples/current/test028_expressions_v4.bc
Binary files a/test/samples/current/test028_expressions_v4.bc and b/test/samples/current/test028_expressions_v4.bc differ
diff --git a/test/samples/current/test029_reject_if.bc b/test/samples/current/test029_reject_if.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test029_reject_if.bc differ
diff --git a/test/samples/current/test030_null.bc b/test/samples/current/test030_null.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test030_null.bc differ
diff --git a/test/samples/current/test031_heterogeneous_equal.bc b/test/samples/current/test031_heterogeneous_equal.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test031_heterogeneous_equal.bc differ
diff --git a/test/samples/current/test032_laziness_closures.bc b/test/samples/current/test032_laziness_closures.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test032_laziness_closures.bc differ
diff --git a/test/samples/current/test033_typeof.bc b/test/samples/current/test033_typeof.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test033_typeof.bc differ
diff --git a/test/samples/current/test034_array_map.bc b/test/samples/current/test034_array_map.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test034_array_map.bc differ
diff --git a/test/samples/current/test035_ffi.bc b/test/samples/current/test035_ffi.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test035_ffi.bc differ
diff --git a/test/samples/current/test036_secp256r1.bc b/test/samples/current/test036_secp256r1.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test036_secp256r1.bc differ
diff --git a/test/samples/current/test037_secp256r1_third_party.bc b/test/samples/current/test037_secp256r1_third_party.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test037_secp256r1_third_party.bc differ
diff --git a/test/samples/current/test038_try_op.bc b/test/samples/current/test038_try_op.bc
new file mode 100644
Binary files /dev/null and b/test/samples/current/test038_try_op.bc differ
