tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

Spec.hs (183643B)


      1 module Main where
      2 
      3 import WorkspaceTests (workspaceTests)
      4 import IOBoundaryTests (ioBoundaryTests)
      5 import PortableCompilerTests (portableCompilerTests)
      6 import ExecutableLoadingTests (executableLoadingTests)
      7 import ScriptedEffects (runScriptedEffects)
      8 import IODriverTests (ioDriverTests)
      9 import LibraryProgramTests (maybeTests, providedLibraries, contractsTests, arithmetic)
     10 import qualified ParserValueTests
     11 import qualified LibraryValueTests
     12 import PortableTestCompiler (compileFixtureFile, compileFixtureDefinitions)
     13 import ReferenceFixtures
     14 import PackageTests (packageTests)
     15 import InteractionInspectionTests (interactionInspectionTests)
     16 import REPL
     17 import Runtime
     18 import TreeObservation (forceTreeShared)
     19 import qualified NodeTable as Nodes
     20 import Wire
     21 import SupportFixture (canonicalEnvs, verifyReuseProviders, verifySupportSources, verifyRelocatedSupportSources,
     22   verifySupportExports, verifySupportDependencies, readFixtureSourceBytes, readTestSource, readPinnedSource)
     23 import ContentStore
     24 import ContentStore.Bundle
     25 import Module.Manifest
     26 import IODriver (IOPermissions(..), checkIOSentinel, runIO, runIOWithEnv, runIOWith, unsafePerms, defaultPerms)
     27 
     28 import Control.Exception      (bracket, evaluate, try, SomeException)
     29 import Control.Concurrent     (forkIO, newEmptyMVar, putMVar, takeMVar)
     30 import System.IO.Unsafe       (unsafePerformIO)
     31 import System.Mem.StableName (makeStableName)
     32 import Data.IORef (IORef, newIORef, modifyIORef', readIORef, atomicModifyIORef')
     33 import Control.Monad         (foldM, forM, forM_, unless)
     34 import Control.Monad.IO.Class (liftIO)
     35 import System.IO.Temp         (withSystemTempDirectory)
     36 import qualified System.Timeout as Timeout
     37 import System.Directory      (createDirectory, createFileLink, doesFileExist, doesDirectoryExist, listDirectory)
     38 import System.FilePath       ((</>))
     39 import Data.Bits              (xor)
     40 import Data.List              (find, isInfixOf, isPrefixOf, stripPrefix)
     41 import Data.Text              (Text, unpack, pack)
     42 import Data.Text.Encoding     (decodeUtf8, encodeUtf8)
     43 import Data.Word              (Word8)
     44 import Test.Tasty
     45 import Test.Tasty.HUnit
     46 
     47 import Data.ByteString (ByteString)
     48 import qualified Data.Foldable as Foldable
     49 import qualified Data.ByteString as BS
     50 import qualified Data.Map as Map
     51 import qualified Data.Sequence as Seq
     52 import qualified Data.Set as Set
     53 import qualified Data.Vector as V
     54 
     55 main :: IO ()
     56 main = do
     57   putStrLn "Reference-artifact tests: authenticated compiler/support snapshots, not working-tree candidate coverage."
     58   defaultMain tests
     59 
     60 tests :: TestTree
     61 tests = testGroup "Tricu Tests"
     62   [ workspaceTests
     63   , ioBoundaryTests
     64   , portableCompilerTests
     65   , executableLoadingTests
     66   , runtimePresentationTests
     67   , interactionInspectionTests
     68   , packageTests
     69   , evaluationDemandTests
     70   , arithmetic
     71   , providedLibraries
     72   , contractsTests
     73   , maybeTests
     74   , fileEval
     75   , demos
     76   , decoding
     77   , byteMarshallingTests
     78   , sourceObjectCodecTests
     79   , sharedTreeDecodingTests
     80   , verifiedTreeLoadingTests
     81   , indexedTreeTransportTests
     82   , verifiedTreePublicationTests
     83   , wireTests
     84   , tricuReaderTests
     85   , LibraryValueTests.byteListUtilities (fixtureExports "bytes") (fixtureExports "base")
     86   , ParserValueTests.binaryParserTests (fixtureExports "binary") (fixtureExports "parser") (fixtureExports "base")
     87   , ParserValueTests.portableParserTests (fixtureExports "parser") (fixtureExports "base")
     88   , locatedLexerTests
     89   , compilerFixtureTests
     90   , digestHexTests
     91   , gateLexerTests
     92   , gateParserTests
     93   , gateProgramTests
     94   , equationHelperTests
     95   , sourceGraphTests
     96   , gateOrderingTests
     97   , gateUnitTests
     98   , gateResolutionTests
     99   , gateCompilationTests
    100   , LibraryValueTests.httpParsingTests (fixtureExports "http")
    101   , httpPipelineTests
    102   , registryContractTests
    103   , registryPipelineTests
    104   , localControlTests
    105   , contentStoreTests
    106   , artifactSafetyTests
    107   , ioDriverTests
    108   ]
    109 
    110 
    111 -- Constructor display is a runtime presentation format, not a frontend AST.
    112 runtimePresentationTests :: TestTree
    113 runtimePresentationTests = testGroup "Runtime presentation"
    114   [ testCase "AST leaf constructor golden" $ formatT AST Leaf @?= "TLeaf"
    115   , testCase "AST stem constructor golden" $ formatT AST (Stem Leaf) @?= "TStem TLeaf"
    116   , testCase "AST nested constructor golden" $
    117       formatT AST (Fork (Stem Leaf) (Fork Leaf Leaf)) @?=
    118         "TFork (TStem TLeaf) (TFork TLeaf TLeaf)"
    119   ]
    120 
    121 -- Demand uses exceptions rather than unbounded divergent reductions.
    122 -- Physical sharing checks the Haskell implementation, not a tree ABI promise.
    123 evaluationDemandTests :: TestTree
    124 evaluationDemandTests = localOption (mkTimeout 30000000) $ testGroup "Evaluation demand boundaries"
    125   [ testCase "constant application discards its argument" $
    126       apply (Fork Leaf (ofNumber 23)) poison @?= ofNumber 23
    127   , testCase "constructor application exposes only the outer constructor" $ do
    128       case apply Leaf poison of
    129         Stem _ -> pure ()
    130         _ -> assertFailure "expected Stem"
    131       case apply (Stem poison) poison of
    132         Fork _ _ -> pure ()
    133         _ -> assertFailure "expected Fork"
    134   , testCase "triage demands shape but not unused handlers or children" $ do
    135       let value = ofNumber 23
    136           constant = Fork Leaf value
    137       apply (Fork (Fork value poison) poison) Leaf @?= value
    138       apply (Fork (Fork poison constant) poison) (Stem poison) @?= value
    139       apply (Fork (Fork poison poison) (Fork Leaf constant))
    140         (Fork poison poison) @?= value
    141       demanded (evaluate (apply (Fork (Fork Leaf Leaf) Leaf) poison))
    142   , testCase "distribution retains the duplicated argument's physical sharing" $ do
    143       -- t (t t) t c -> t c (t c). Both occurrences reference c.
    144       input <- newIORef (Fork (ofNumber 23) (ofNumber 24)) >>= readIORef
    145       case apply (Fork (Stem Leaf) Leaf) input of
    146         Fork left (Stem right) -> do
    147           a <- evaluate left >>= makeStableName
    148           b <- evaluate right >>= makeStableName
    149           assertBool "distribution copied the argument" (a == b)
    150         _ -> assertFailure "unexpected distribution result"
    151   , testCase "host list decoding checks the whole spine but not elements" $ do
    152       fmap length (toList (Fork poison Leaf)) @?= Right 1
    153       demanded (evaluate (toList (Fork Leaf poison)))
    154   , testCase "complete flat serialization demands hidden children" $
    155       demanded (evaluate (BS.length (encodeTreeTerm (Fork Leaf poison))))
    156   , testCase "complete indexed serialization demands hidden children" $
    157       demanded (buildBundleShared [("root", Fork Leaf poison)])
    158   , testCase "full observation visits a shared depth-80 DAG within a bounded interval" $ do
    159       let tower 0 = Leaf
    160           tower n = let child = tower (n - 1) in Fork child child
    161       outcome <- Timeout.timeout 2000000 (forceTreeShared (tower (80 :: Int)))
    162       outcome @?= Just ()
    163   , testCase "full observation demands hidden children and does not retain failures" $ do
    164       demanded (forceTreeShared (Fork Leaf poison))
    165       forceTreeShared (Fork Leaf (Stem Leaf))
    166   , testCase "full observation handles deep trees without expanding a recursive call stack" $ do
    167       let chain = foldr (\_ child -> Stem child) Leaf [1..65536 :: Int]
    168       outcome <- Timeout.timeout 2000000 (forceTreeShared chain)
    169       outcome @?= Just ()
    170   , testCase "stable local bindings and ordinary applications discard unused inputs" $ do
    171       forM_ ["main input = (ignored : 23) input",
    172              "main input = let unused = input in 23"] $ \source -> do
    173         function <- compileLocalControl [] source
    174         apply function poison @?= ofNumber 23
    175   , testCase "stable function consumes a finite prefix of an infinite producer" $ do
    176       function <- compileLocalControl [] $ unlines
    177         [ "main input = (let rec ones x = t x (ones x) in case (ones input) of"
    178         , "  [head | _] = head"
    179         , "  _ = 0)" ]
    180       apply function (ofNumber 23) @?= ofNumber 23
    181   ]
    182   where
    183     poison = error "evaluation-demand sentinel" :: T
    184     demanded :: IO a -> Assertion
    185     demanded action = do
    186       outcome <- try (action >> pure ()) :: IO (Either SomeException ())
    187       case outcome of
    188         Left err -> assertBool (show err) ("evaluation-demand sentinel" `isInfixOf` show err)
    189         Right () -> assertFailure "expected the observer to demand the poisoned subtree"
    190 
    191 -- Host-only negative controls: no source compiler or retained toolchain needed.
    192 artifactSafetyTests :: TestTree
    193 artifactSafetyTests = testGroup "artifact safety" $
    194   [ testGroup "alias components"
    195       [ testCase (show name) $ withSystemTempDirectory "tricu-alias-safety" $ \dir -> do
    196           let store = StorePath (dir </> "store")
    197               ref = treeTermRef (hashObject treeTermDomain (encodeTreeTerm Leaf))
    198           forM_ [NameAlias, ModuleAlias, PackageAlias] $ \kind -> do
    199             blocked "invalid alias name" (writeAlias store kind name ref)
    200             blocked "invalid alias name" (readAlias store kind name)
    201           doesDirectoryExist (unStorePath store) >>= (@?= False)
    202       | name <- ["", ".", "..", "../escape", "a/../../escape", "/absolute",
    203                  "a\\b", "C:escape", "C:\\escape", "a\NULb", "a\nb", "a\rb"]
    204       ]
    205   , testCase "flat import counts exponential DAGs without expansion" $ do
    206       let base = buildBundle [("root", Leaf)]
    207           graph = base { bundleNodes = Seq.fromList (BNLeaf : [BNFork (i-1) (i-1) | i <- [1..10000]])
    208                        , bundleRoots = [10000]
    209                        , bundleManifest = (bundleManifest base) { manifestRoots = [BundleRoot 10000 "default"],
    210                            manifestExports = [BundleExport "root" 10000 "term" "arboricx.abi.tree.v1"] } }
    211       case checkFlatImportSize defaultFlatImportLimit graph of
    212         Left err -> assertBool err ("flat import byte limit exceeded" `isInfixOf` err)
    213         Right () -> assertFailure "expanded size was not bounded"
    214   , testCase "flat byte accounting matches exact small serializations" $
    215       forM_ [Leaf, Stem Leaf, Fork (Stem Leaf) (Stem Leaf)] $ \tree -> do
    216         let bundle = buildBundle [("a", tree), ("b", tree)]
    217             size = 2 * fromIntegral (BS.length (encodeTreeTerm tree))
    218         checkFlatImportSize size bundle @?= Right ()
    219         assertBool "one byte under budget must fail" (either (const True) (const False) (checkFlatImportSize (size-1) bundle))
    220   , testCase "duplicate export names rejected by bundle and stored manifest readers" $ do
    221       let bundle = buildBundle [("same", Leaf), ("same", Stem Leaf)]
    222           manifest = ModuleManifest [] [ModuleExport "same" (treeTermRef (nodeHash NLeaf)) "arboricx.abi.tree.v1",
    223                                         ModuleExport "same" (treeTermRef (nodeHash NLeaf)) "arboricx.abi.tree.v1"]
    224       verifyBundle bundle @?= Left "duplicate export name"
    225       decodeManifest (encodeManifest manifest) @?= Left "duplicate export name"
    226   , testCase "qualified and symbolic aliases remain usable" $
    227       withSystemTempDirectory "tricu-alias-valid" $ \dir -> do
    228         let store = StorePath dir
    229             ref = treeTermRef (hashObject treeTermDomain (encodeTreeTerm Leaf))
    230         forM_ ["native_math.isNat?", "++", "hello world", "λ", ".hidden", "a..b"] $ \name -> do
    231           writeAlias store NameAlias name ref
    232           readAlias store NameAlias name >>= (@?= Just ref)
    233   , testCase "alias absolute paths cannot read or overwrite an outside sentinel" $
    234       withSystemTempDirectory "tricu-alias-sentinel" $ \dir -> do
    235         let store = StorePath (dir </> "store")
    236             outside = dir </> "sentinel"
    237             name = pack outside
    238             ref = treeTermRef (hashObject treeTermDomain (encodeTreeTerm Leaf))
    239         BS.writeFile outside "unchanged"
    240         blocked "invalid alias name" (writeAlias store NameAlias name ref)
    241         blocked "invalid alias name" (readAlias store NameAlias name)
    242         BS.readFile outside >>= (@?= "unchanged")
    243   , testGroup "object path hashes"
    244       [ testCase (show h) $ withSystemTempDirectory "tricu-hash-safety" $ \dir ->
    245           do
    246             blocked "invalid object hash" (getObject (StorePath dir) h)
    247             blocked "invalid object hash" (evaluate (objectPath (StorePath dir) h))
    248       | h <- ["", "../escape", "../../escape", "/absolute", "abc", pack (replicate 64 'A'),
    249               pack (replicate 63 'a'), pack (replicate 65 'a'), pack (replicate 64 'g')]
    250       ]
    251   , testCase "object paths cannot read an absolute outside sentinel" $
    252       withSystemTempDirectory "tricu-hash-sentinel" $ \dir -> do
    253         let outside = dir </> "sentinel"
    254         BS.writeFile outside "not a CAS object"
    255         blocked "invalid object hash" (getObject (StorePath (dir </> "store")) (pack outside))
    256   , testCase "cached authenticated objects are immutable snapshots" $ fresh $ \store -> do
    257       h <- putTreeTerm store Leaf
    258       resolver <- cachedFilesystemResolver store
    259       resolverObject resolver (treeTermRef h) >>= (@?= Just (encodeTreeTerm Leaf))
    260       BS.writeFile (objectPath store h) (encodeTreeTerm (Stem Leaf))
    261       resolverObject resolver (treeTermRef h) >>= (@?= Just (encodeTreeTerm Leaf))
    262       blocked "digest mismatch" (resolverObject (filesystemResolver store) (treeTermRef h))
    263   , testCase "object resolution verifies bytes from custom resolver callbacks" $ fresh $ \store -> do
    264       h <- putTreeTerm store Leaf
    265       mh <- putManifest store (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"])
    266       let resolver = (filesystemResolver store) { resolverObject = \_ -> pure (Just (encodeTreeTerm (Stem Leaf))) }
    267       blocked "digest mismatch" (resolveObjectByHash resolver (unDomain treeTermDomain) h)
    268   , testGroup "execution contract"
    269       [ testCase label $ do
    270           let base = buildBundle [("first", Leaf), ("second", Stem Leaf)]
    271               bundle = base { bundleManifest = change (bundleManifest base), bundleManifestBytes = BS.empty }
    272           case verifyBundle bundle of
    273             Left err -> assertBool err (needle `isInfixOf` err)
    274             Right () -> assertFailure "unsupported execution contract accepted"
    275       | (label, needle, change) <-
    276           [("evaluation", "runtime evaluation", \m -> m { manifestRuntime = (manifestRuntime m) { runtimeEvaluation = "call-by-value" } }),
    277            ("export kind", "export kind", \m -> m { manifestExports = map (\e -> e { exportKind = "foreign" }) (manifestExports m) }),
    278            ("second export ABI", "export ABI", \m -> m { manifestExports = case manifestExports m of
    279                [a,b] -> [a,b { exportAbi = "foreign.abi" }]; es -> es })]
    280       ]
    281   ] ++
    282   [ testGroup label
    283       [ testCase "tampered manifest" $ fresh $ \store -> do
    284           h <- putManifest store (ModuleManifest [] [])
    285           BS.writeFile (objectPath store h) (encodeManifest (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"]))
    286           resolver <- makeResolver store
    287           blocked "digest mismatch" (resolveManifest resolver h)
    288           blocked "digest mismatch" (getManifest store h)
    289       , testCase "tampered exported term" $ fresh $ \store -> do
    290           h <- putTreeTerm store Leaf
    291           mh <- putManifest store (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"])
    292           BS.writeFile (objectPath store h) (encodeTreeTerm (Stem Leaf))
    293           resolver <- makeResolver store
    294           blocked "digest mismatch" (resolveObjectByHash resolver (unDomain treeTermDomain) h)
    295           blocked "digest mismatch" (getTreeTerm store h)
    296       , testCase "tampered Merkle child" $ fresh $ \store -> do
    297           h <- putTree store (Stem Leaf)
    298           BS.writeFile (objectPath store (nodeHash NLeaf)) (serializeNode (NStem (pack (replicate 64 '0'))))
    299           resolver <- makeResolver store
    300           blocked "digest mismatch" (resolveTree resolver h)
    301           blocked "digest mismatch" (getTree store h)
    302       , testCase "tampered self-cycle rejected before hydration" $ fresh $ \store -> do
    303           h <- putTree store Leaf
    304           BS.writeFile (objectPath store h) (serializeNode (NStem h))
    305           resolver <- makeResolver store
    306           completed <- Timeout.timeout 1000000 (blocked "digest mismatch" (resolveTree resolver h))
    307           completed @?= Just ()
    308       , testCase "failed verification never poisons the cache" $ fresh $ \store -> do
    309           h <- putTreeTerm store Leaf
    310           resolver <- makeResolver store
    311           BS.writeFile (objectPath store h) (encodeTreeTerm (Stem Leaf))
    312           blocked "digest mismatch" (resolverObject resolver (treeTermRef h))
    313           BS.writeFile (objectPath store h) (encodeTreeTerm Leaf)
    314           resolverObject resolver (treeTermRef h) >>= (@?= Just (encodeTreeTerm Leaf))
    315       , testCase "wrong object domain" $ fresh $ \store -> do
    316           h <- putTreeTerm store Leaf
    317           resolver <- makeResolver store
    318           blocked "digest mismatch" (resolverObject resolver (ObjectRef (unDomain manifestDomain) h))
    319       -- Export ABI rejection is exercised against the production executable in
    320       -- executable_arguments.py, not the retired source-module resolver.
    321       , testCase "valid objects and genuine missing objects" $ fresh $ \store -> do
    322           h <- putTreeTerm store (Stem Leaf)
    323           mh <- putManifest store (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"])
    324           nh <- putTree store (Stem Leaf)
    325           resolver <- makeResolver store
    326           resolveManifest resolver mh >>= (@?= Just (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"]))
    327           resolveTree resolver nh >>= (@?= Just (Stem Leaf))
    328           resolveObjectByHash resolver (unDomain treeTermDomain) h >>= (@?= Just (encodeTreeTerm (Stem Leaf)))
    329           loadExecutableObject store (treeTermRef h) >>= (@?= Stem Leaf)
    330           resolverObject resolver (treeTermRef (pack (replicate 64 '0'))) >>= (@?= Nothing)
    331       ]
    332       | (label, makeResolver) <- [("filesystem integrity", pure . filesystemResolver), ("cached integrity", cachedFilesystemResolver)]
    333   ]
    334   where
    335     treeTermRef = ObjectRef (unDomain treeTermDomain)
    336     fresh f = withSystemTempDirectory "tricu-integrity" (f . StorePath)
    337     blocked needle action = do
    338       outcome <- try (action >> pure ()) :: IO (Either SomeException ())
    339       case outcome of
    340         Left err -> assertBool (show err) (needle `isInfixOf` show err)
    341         Right () -> assertFailure ("expected rejection: " ++ needle)
    342 
    343 sharedTreeDecodingTests :: TestTree
    344 sharedTreeDecodingTests = testGroup "shared inert tree decoding"
    345   [ testCase "small trees preserve exact structure and canonical bytes" $
    346       forM_ (trees 3) $ \tree -> do
    347         let bytes = encodeTreeTerm tree
    348         decodeTreeTermShared bytes @?= Right tree
    349         fmap encodeTreeTerm (decodeTreeTermShared bytes) @?= Right bytes
    350   , testCase "integer interning distinguishes constructors, pair order and wide IDs" $ do
    351       let keys = [Nodes.LeafKey] ++ map Nodes.StemKey values ++
    352                  [Nodes.ForkKey a b | a <- values, b <- values]
    353           values = [minBound, -1, 0, 1, 65535, maxBound] ++
    354                    map fromInteger [2147483647, 2147483648, 4294967295, 4294967296]
    355           entries = zip keys [(0 :: Int)..]
    356           table = foldl (\acc (key, value) -> Nodes.insert key value acc) Nodes.empty entries
    357           expected = Map.fromList entries
    358       forM_ (Map.toList expected) $ \(key, value) ->
    359         Nodes.lookup key table @?= Just value
    360       Nodes.lookup (Nodes.ForkKey 17 19) table @?= Nothing
    361       Nodes.lookup (Nodes.StemKey 17) table @?= Nothing
    362       let updated = Nodes.insert (Nodes.ForkKey 0 1) 999 table
    363       Nodes.lookup (Nodes.ForkKey 0 1) updated @?= Just 999
    364       Nodes.lookup (Nodes.ForkKey 1 0) updated @?= Map.lookup (Nodes.ForkKey 1 0) expected
    365   , testCase "deep shared decoding preserves bytes without recursive reference decoding" $ do
    366       let bytes = BS.replicate 65536 1 <> BS.singleton 0
    367       decoded <- either assertFailure pure (decodeTreeTermShared bytes)
    368       encodeTreeTerm decoded @?= bytes
    369   , testCase "malformed bytes retain ordinary decoder errors" $
    370       forM_ (concat [sequence (replicate n [0, 1, 2, 255]) | n <- [0..4]]) $ \tags ->
    371         decodeTreeTermShared (BS.pack tags) @?= decodeTreeTerm (BS.pack tags)
    372   , testCase "repeated branches regain physical sharing without application" $ do
    373       let tower 0 = Fork (Stem Leaf) Leaf
    374           tower n = let child = tower (n - 1) in Fork child child
    375           value = tower (14 :: Int)
    376       decoded <- either assertFailure pure (decodeTreeTermShared (encodeTreeTerm value))
    377       let check (Fork left right) = do
    378             a <- evaluate left >>= makeStableName
    379             b <- evaluate right >>= makeStableName
    380             assertBool "equal branches were separately allocated" (a == b)
    381           check _ = assertFailure "missing fork"
    382       check decoded
    383       case decoded of
    384         Fork left _ -> check left
    385         _ -> assertFailure "missing root"
    386   ]
    387   where
    388     trees 0 = [Leaf]
    389     trees n = let children = trees (n - 1)
    390               in Leaf : map Stem children ++ [Fork a b | a <- children, b <- children]
    391 
    392 -- Indexed IO transports inert normal forms. The tiny exponential logical tree
    393 -- is deliberately never unfolded for equality/encoding by these controls.
    394 indexedTreeTransportTests :: TestTree
    395 indexedTreeTransportTests = testGroup "indexed tree transport"
    396   [ testCase "canonical bytes agree with independent ordinary writer" $
    397       forM_ (trees 2) $ \tree -> do
    398         encoded <- encodeIndexedTree tree
    399         encoded @?= encodeBundle (buildBundle [("root", tree)])
    400         decodeIndexedTree encoded @?= Right tree
    401   , testCase "shared depth-80 tree encodes within a bounded interval" $ do
    402       let tower 0 = Leaf
    403           tower n = let child = tower (n - 1) in Fork child child
    404       measured <- Timeout.timeout 2000000 $ do
    405         payload <- encodeIndexedTree (tower (80 :: Int))
    406         size <- evaluate (BS.length payload)
    407         assertBool "DAG unexpectedly expanded" (size < 2048)
    408         bundle <- either assertFailure pure (decodeBundle payload)
    409         Seq.length (bundleNodes bundle) @?= 81
    410         value <- either assertFailure pure (decodeIndexedTree payload)
    411         case value of
    412           Fork left right -> do
    413             a <- evaluate left >>= makeStableName
    414             b <- evaluate right >>= makeStableName
    415             assertBool "decoded branches lost sharing" (a == b)
    416           _ -> assertFailure "missing fork"
    417       measured @?= Just ()
    418   , testCase "shared writer is name-neutral and byte-identical to indexed import" $
    419       withSystemTempDirectory "tricu-indexed-import" $ \dir -> do
    420         let tree = Fork (Stem Leaf) (Stem Leaf)
    421         encoded <- encodeIndexedTree tree
    422         [(name, pin)] <- unpackIndexedBundleToStore (StorePath dir)
    423           (encodeBundle (buildBundle [("publicName", tree)]))
    424         name @?= "publicName"
    425         pin @?= hashObject indexedTreeDomain encoded
    426   , testCase "hash, no-replace publication, repeat and read exact inert tree" $
    427       withSystemTempDirectory "tricu-indexed-io" $ \dir -> do
    428         let tree = Fork (Stem (Fork Leaf Leaf)) (Fork Leaf (Stem Leaf))
    429             path = dir </> "object"
    430         payload <- encodeIndexedTree tree
    431         let pin = hashObject indexedTreeDomain payload
    432         action unsafePerms 51 tree >>= (@?= Right (ioOkResult (ofString (unpack pin))))
    433         writeIndexed unsafePerms path pin tree >>= (@?= Right (ioOkResult (Stem Leaf)))
    434         BS.readFile path >>= (@?= payload)
    435         writeIndexed unsafePerms path pin tree >>= (@?= Right (ioOkResult Leaf))
    436         readIndexed unsafePerms path pin >>= (@?= Right (ioOkResult tree))
    437   , testCase "read checks permission, canonical digest, and domain" $
    438       withSystemTempDirectory "tricu-indexed-denied" $ \dir -> do
    439         let path = dir </> "object"
    440         payload <- encodeIndexedTree Leaf
    441         BS.writeFile path payload
    442         let pin = hashObject indexedTreeDomain payload
    443         readIndexed defaultPerms path pin >>= (@?= Right (ioErrResult "permission denied"))
    444         readIndexed unsafePerms path "BAD" >>= (@?= Right (ioErrResult "invalid CAS digest"))
    445         readIndexed unsafePerms path (hashObject treeTermDomain payload)
    446           >>= (@?= Right (ioErrResult "CAS digest mismatch"))
    447   , testCase "verification precedes malformed indexed payload decoding" $
    448       withSystemTempDirectory "tricu-indexed-bad" $ \dir -> do
    449         let path = dir </> "object"
    450             payload = "bad bundle"
    451         BS.writeFile path payload
    452         readIndexed unsafePerms path (pack (replicate 64 '0'))
    453           >>= (@?= Right (ioErrResult "CAS digest mismatch"))
    454         readIndexed unsafePerms path (hashObject indexedTreeDomain payload)
    455           >>= (@?= Right (ioErrResult "invalid indexed tree"))
    456         let named = encodeBundle (buildBundle [("other", Leaf)])
    457         BS.writeFile path named
    458         readIndexed unsafePerms path (hashObject indexedTreeDomain named)
    459           >>= (@?= Right (ioErrResult "invalid indexed tree"))
    460   , testCase "publication needs both permissions and never overwrites corruption" $
    461       withSystemTempDirectory "tricu-indexed-publish" $ \dir -> do
    462         let path = dir </> "object"
    463         payload <- encodeIndexedTree Leaf
    464         let pin = hashObject indexedTreeDomain payload
    465         forM_ [defaultPerms { allowRead = [dir] }, defaultPerms { allowWrite = [dir] }] $ \perms ->
    466           writeIndexed perms path pin Leaf >>= (@?= Right (ioErrResult "permission denied"))
    467         doesFileExist path >>= (@?= False)
    468         writeIndexed unsafePerms path (pack (replicate 64 '0')) Leaf
    469           >>= (@?= Right (ioErrResult "CAS digest mismatch"))
    470         doesFileExist path >>= (@?= False)
    471         BS.writeFile path "existing corruption"
    472         writeIndexed unsafePerms path pin Leaf
    473           >>= (@?= Right (ioErrResult "CAS object exists with different bytes"))
    474         BS.readFile path >>= (@?= "existing corruption")
    475   ]
    476   where
    477     trees 0 = [Leaf]
    478     trees n = let xs = trees (n - 1) in Leaf : map Stem xs ++ [Fork a b | a <- xs, b <- xs]
    479     action perms tag payload = runIO perms $
    480       Fork (ofString "tricuIO") (Fork (ofNumber 1) (Fork (ofNumber tag) payload))
    481     readIndexed perms path pin = action perms 50 (Fork (ofString path) (ofString (unpack pin)))
    482     writeIndexed perms path pin tree = action perms 52
    483       (Fork (ofString path) (Fork (ofString (unpack pin)) tree))
    484 
    485 -- Small host/unit codec gate, not compiler-closure compilation. Raw trees
    486 -- must be inspected/encoded without invoking them as functions.
    487 verifiedTreeLoadingTests :: TestTree
    488 verifiedTreeLoadingTests = testGroup "verified tree loading"
    489   [ testCase "exact inert tree, including function-shaped forks" $
    490       withSystemTempDirectory "tricu-tree-load" $ \dir -> do
    491         let value = Fork (Stem (Fork Leaf Leaf)) (Fork Leaf (Stem Leaf))
    492             payload = encodeTreeTerm value
    493             path = dir </> "object"
    494         BS.writeFile path payload
    495         readTerm (defaultPerms { allowRead = [dir] }) path
    496           (unpack (hashObject treeTermDomain payload)) >>= (@?= Right (ioOkResult value))
    497   , testCase "read denied even with write permission" $
    498       withSystemTempDirectory "tricu-tree-denied" $ \dir -> do
    499         let payload = BS.singleton 0
    500             path = dir </> "object"
    501         BS.writeFile path payload
    502         readTerm (defaultPerms { allowWrite = [dir] }) path
    503           (unpack (hashObject treeTermDomain payload)) >>= (@?= Right (ioErrResult "permission denied"))
    504   , testCase "symlink does not bypass read permission" $
    505       withSystemTempDirectory "tricu-tree-link" $ \dir -> do
    506         let allowed = dir </> "allowed"
    507             denied = dir </> "outside"
    508             link = allowed </> "link"
    509             payload = BS.singleton 0
    510         createDirectory allowed
    511         BS.writeFile denied payload
    512         createFileLink denied link
    513         readTerm (defaultPerms { allowRead = [allowed] }) link
    514           (unpack (hashObject treeTermDomain payload)) >>= (@?= Right (ioErrResult "permission denied"))
    515   , testCase "missing file" $
    516       withSystemTempDirectory "tricu-tree-missing" $ \dir ->
    517         readTerm (defaultPerms { allowRead = [dir] }) (dir </> "missing")
    518           (replicate 64 '0') >>= (@?= Right (ioErrResult "does not exist"))
    519   , testCase "mismatched digest precedes decoding" $
    520       withSystemTempDirectory "tricu-tree-mismatch" $ \dir -> do
    521         let path = dir </> "object"
    522         BS.writeFile path (BS.singleton 255)
    523         readTerm unsafePerms path (replicate 64 '0') >>= (@?= Right (ioErrResult "CAS digest mismatch"))
    524   , testCase "wrong domain rejected" $
    525       withSystemTempDirectory "tricu-tree-domain" $ \dir -> do
    526         let payload = BS.singleton 0
    527             path = dir </> "object"
    528         BS.writeFile path payload
    529         readTerm unsafePerms path (unpack (hashObject (Domain "wrong.domain") payload))
    530           >>= (@?= Right (ioErrResult "CAS digest mismatch"))
    531   , testCase "digest must be canonical lowercase hexadecimal" $
    532       forM_ ["bad", replicate 64 'G', replicate 64 'A', replicate 63 '0'] $ \digest ->
    533         readTerm unsafePerms "/definitely-missing" digest >>= (@?= Right (ioErrResult "invalid CAS digest"))
    534   , testCase "malformed canonical payloads rejected after valid digest" $
    535       withSystemTempDirectory "tricu-tree-malformed" $ \dir ->
    536         forM_ [BS.empty, BS.pack [1], BS.pack [2,0], BS.pack [3], BS.pack [0,0]] $ \payload -> do
    537           let path = dir </> "object"
    538           BS.writeFile path payload
    539           readTerm unsafePerms path (unpack (hashObject treeTermDomain payload))
    540             >>= (@?= Right (ioErrResult "invalid canonical tree-term"))
    541   , testCase "invalid argument strings" $ do
    542       runAction unsafePerms (Stem Leaf) (ofString (replicate 64 '0'))
    543         >>= (@?= Right (ioErrResult "invalid string"))
    544   , testCase "readTreeTerm library constructor" $
    545       withSystemTempDirectory "tricu-tree-constructor" $ \dir -> do
    546         let value = Stem Leaf
    547             payload = encodeTreeTerm value
    548             path = dir </> "object"
    549         BS.writeFile path payload
    550         let io = fixtureExports "io"
    551             action = apply (io Map.! "io") (foldl apply (io Map.! "readTreeTerm")
    552               [ofString path, ofString (unpack (hashObject treeTermDomain payload))])
    553         runIO (defaultPerms { allowRead = [dir] }) action >>= (@?= Right (ioOkResult value))
    554   ]
    555   where
    556     readTerm perms path digest = runAction perms (ofString path) (ofString digest)
    557     runAction perms path digest = runIO perms $
    558       Fork (ofString "tricuIO") (Fork (ofNumber 1) (Fork (ofNumber 34) (Fork path digest)))
    559 
    560 verifiedTreePublicationTests :: TestTree
    561 verifiedTreePublicationTests = testGroup "verified tree publication"
    562   [ testCase "canonical hashing agrees with independent bytes" $ do
    563       let value = Fork (Stem Leaf) (Fork Leaf (Stem Leaf))
    564           golden = BS.pack [2,1,0,2,0,1,0]
    565       encodeTreeTerm value @?= golden
    566       hashTerm value >>= (@?= Right (ioOkResult (ofString (unpack (hashObject treeTermDomain golden)))))
    567   , testCase "new publication has exact bytes and no temporary residue" $
    568       withSystemTempDirectory "tricu-tree-publish" $ \dir -> do
    569         let path = dir </> "object"
    570             tree = Fork (Stem Leaf) Leaf
    571             payload = BS.pack [2,1,0,0]
    572         publish (permissions dir) path (digest payload) tree >>= (@?= Right (ioOkResult (Stem Leaf)))
    573         BS.readFile path >>= (@?= payload)
    574         listDirectory dir >>= (@?= ["object"])
    575   , testCase "existing identical object returns false" $
    576       withSystemTempDirectory "tricu-tree-existing" $ \dir -> do
    577         let path = dir </> "object"
    578             tree = Stem Leaf
    579             payload = BS.pack [1,0]
    580         BS.writeFile path payload
    581         publish (permissions dir) path (digest payload) tree >>= (@?= Right (ioOkResult Leaf))
    582         BS.readFile path >>= (@?= payload)
    583   , testCase "corrupt winner is not replaced" $
    584       withSystemTempDirectory "tricu-tree-corrupt" $ \dir -> do
    585         let path = dir </> "object"
    586             old = BS.pack [255]
    587         BS.writeFile path old
    588         publish (permissions dir) path (digest (BS.singleton 0)) Leaf
    589           >>= (@?= Right (ioErrResult "CAS object exists with different bytes"))
    590         BS.readFile path >>= (@?= old)
    591         listDirectory dir >>= (@?= ["object"])
    592   , testCase "mismatch fails before publication" $
    593       withSystemTempDirectory "tricu-tree-wrong" $ \dir -> do
    594         publish (permissions dir) (dir </> "object") (replicate 64 '0') Leaf
    595           >>= (@?= Right (ioErrResult "CAS digest mismatch"))
    596         listDirectory dir >>= (@?= [])
    597   , testCase "write denied without forcing tree" $
    598       withSystemTempDirectory "tricu-tree-write-denied" $ \dir -> do
    599         publish (defaultPerms { allowRead = [dir] }) (dir </> "object")
    600           (digest (BS.singleton 0)) (error "denied write must not force tree")
    601           >>= (@?= Right (ioErrResult "permission denied"))
    602         listDirectory dir >>= (@?= [])
    603   , testCase "read denied without forcing tree" $
    604       withSystemTempDirectory "tricu-tree-read-denied" $ \dir -> do
    605         publish (defaultPerms { allowWrite = [dir] }) (dir </> "object")
    606           (digest (BS.singleton 0)) (error "denied read must not force tree")
    607           >>= (@?= Right (ioErrResult "permission denied"))
    608         listDirectory dir >>= (@?= [])
    609   , testCase "symlink does not bypass write permission" $
    610       withSystemTempDirectory "tricu-tree-write-link" $ \dir -> do
    611         let allowed = dir </> "allowed"
    612             outside = dir </> "outside"
    613             link = allowed </> "link"
    614             payload = BS.singleton 0
    615         createDirectory allowed
    616         BS.writeFile outside payload
    617         createFileLink outside link
    618         publish (permissions allowed) link (digest payload) Leaf
    619           >>= (@?= Right (ioErrResult "permission denied"))
    620         BS.readFile outside >>= (@?= payload)
    621   , testCase "invalid digest fails without filesystem effects" $
    622       withSystemTempDirectory "tricu-tree-bad-digest" $ \dir -> do
    623         forM_ ["bad", replicate 64 'G', replicate 64 'A'] $ \bad ->
    624           publish (permissions dir) (dir </> "object") bad Leaf
    625             >>= (@?= Right (ioErrResult "invalid CAS digest"))
    626         listDirectory dir >>= (@?= [])
    627   , testCase "parent creation remains explicit" $
    628       withSystemTempDirectory "tricu-tree-parent" $ \dir -> do
    629         publish (permissions dir) (dir </> "missing" </> "object") (digest (BS.singleton 0)) Leaf
    630           >>= (@?= Right (ioErrResult "does not exist"))
    631         listDirectory dir >>= (@?= [])
    632   , testCase "concurrent publishers have one winner" $
    633       withSystemTempDirectory "tricu-tree-race" $ \dir -> do
    634         let tree = iterate (\t -> Fork t t) Leaf !! 12
    635             bytes = encodeTreeTerm tree
    636             path = dir </> "object"
    637             action = publish (permissions dir) path (digest bytes) tree
    638         a <- newEmptyMVar
    639         b <- newEmptyMVar
    640         _ <- forkIO (action >>= putMVar a)
    641         _ <- forkIO (action >>= putMVar b)
    642         results <- sequence [takeMVar a, takeMVar b]
    643         length (filter (== Right (ioOkResult (Stem Leaf))) results) @?= 1
    644         length (filter (== Right (ioOkResult Leaf)) results) @?= 1
    645         BS.readFile path >>= (@?= bytes)
    646         listDirectory dir >>= (@?= ["object"])
    647   , testCase "sourceObjectPutTree creates canonical shards and reuses objects" $
    648       withSystemTempDirectory "tricu-tree-library" $ \dir -> do
    649         env <- evaluateCompilerFile "./lib/source/object.tri"
    650         let root = dir </> "cas"
    651             tree = Fork Leaf (Stem Leaf)
    652             payload = encodeBundle (buildBundle [("root", tree)])
    653             expected = unpack (hashObject indexedTreeDomain payload)
    654             action = sentinel (apply (apply (env Map.! "sourceObjectPutTree") (ofString root)) tree)
    655             path = root </> "objects" </> take 3 expected </> expected
    656         runIO (permissions dir) action >>= (@?= Right (ioOkResult (ofString expected)))
    657         runIO (permissions dir) action >>= (@?= Right (ioOkResult (ofString expected)))
    658         BS.readFile path >>= (@?= payload)
    659   ]
    660   where
    661     digest = unpack . hashObject treeTermDomain
    662     permissions dir = defaultPerms { allowRead = [dir], allowWrite = [dir] }
    663     sentinel action = Fork (ofString "tricuIO") (Fork (ofNumber 1) action)
    664     hashTerm tree = runIO defaultPerms (sentinel (Fork (ofNumber 35) tree))
    665     publish perms path expected tree = runIO perms $
    666       sentinel (Fork (ofNumber 36) (Fork (ofString path) (Fork (ofString expected) tree)))
    667 
    668 sourceObjectCodecTests :: TestTree
    669 sourceObjectCodecTests = testGroup "Portable tree object codec"
    670   [ testCase "canonical preorder bytes, suffix law and complete round trip" $
    671       withSystemTempDirectory "source-object-codec" $ \dir -> do
    672         env <- evaluateCompilerFile "./lib/source/object.tri"
    673         let encoder = env Map.! "sourceObjectEncode"
    674             fixed = env Map.! "sourceObjectEncodeOnto"
    675             decoder = env Map.! "sourceObjectDecodeComplete"
    676             suffix = BS.pack [7, 0, 255]
    677             seeds = [Leaf, Stem Leaf, Fork Leaf Leaf]
    678             small = seeds ++ [Fork l r | l <- seeds, r <- seeds]
    679             leftHeavy = iterate (\tree -> Fork tree Leaf) Leaf !! 64
    680             rightHeavy = iterate (Fork Leaf) Leaf !! 64
    681             balanced = iterate (\tree -> Fork tree tree) Leaf !! 8
    682         forM_ (small ++ [leftHeavy, rightHeavy, balanced]) $ \tree -> do
    683           let bytes = encodeTreeTerm tree
    684               encoded = apply encoder tree
    685           toBytes encoded @?= Right bytes
    686           toBytes (apply (apply fixed tree) (ofBytes suffix)) @?=
    687             Right (bytes <> suffix)
    688           apply decoder encoded @?= Fork (Stem Leaf) (Fork tree Leaf)
    689   ]
    690 
    691 fileEval :: TestTree
    692 fileEval = testGroup "Portable source file tests"
    693   [ testCase "Forks" $ check "./test/fork.tri" (Fork Leaf Leaf)
    694   , testCase "File ends with comment" $ check "./test/comments-1.tri" (Fork (Stem Leaf) Leaf)
    695   , testCase "Mapping and Equality" $ check "./test/map.tri" (Stem Leaf)
    696   , testCase "Eval and decoding string" $ check "./test/string.tri" (ofString "String test!")
    697   ]
    698   where
    699     check path expected = do
    700       source <- readFile path
    701       let external = ofList [Fork (ofString name) value | (name,value) <- Map.toList (fixtureExports "prelude")]
    702       foldl apply (fixtureExports "source.unit" Map.! "compileGateProgram")
    703         [external, ofString source] @?= parserOk expected Leaf
    704 
    705 -- All of our demo tests are also module tests
    706 demos :: TestTree
    707 demos = testGroup "Test provided demo functionality"
    708   [ testCase "Constructor-safe lowering matches the authored Boolean not tree" $ do
    709       env <- evaluateCompilerFile "./demos/equality.tri"
    710       -- Constructor-only K pruning intentionally changes inspectable function
    711       -- trees. This specimen now equals its authored TC form; that is not a
    712       -- structural-equivalence promise for arbitrary extensional functions.
    713       env Map.! "main" @?= Stem Leaf
    714       env Map.! "not_TC?" @?=
    715         Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) (Fork Leaf (Fork Leaf Leaf))
    716       env Map.! "not_Lambda?" @?= env Map.! "not_TC?"
    717       forM_ ["not_TC?", "not_Lambda?"] $ \name -> do
    718         apply (env Map.! name) Leaf @?= Stem Leaf
    719         apply (env Map.! name) (Stem Leaf) @?= Leaf
    720       env Map.! "bothTrueEqual?" @?= Stem Leaf
    721       env Map.! "bothFalseEqual?" @?= Stem Leaf
    722   , testCase "Convert values back to source code demo" $ do
    723       res     <- liftIO $ ((Map.! "main") <$> evaluateCompilerFile "./demos/toSource.tri")
    724       -- Native portable foundation trees, not the Haskell recovery cohort.
    725       decodeResult res @?= "\"(t (t (t t) (t t t)) (t t (t t t)))\""
    726   , testCase "Determining the size of stable-compiled functions" $ do
    727       env <- evaluateCompilerFile "./demos/size.tri"
    728       -- Count the actual executable independently of the compiled size worker.
    729       -- This is a representation check of the retained stable artifact,
    730       -- not a parity requirement across compiler backends.
    731       let nodes Leaf = 1 :: Integer
    732           nodes (Stem child) = 1 + nodes child
    733           nodes (Fork left right) = 1 + nodes left + nodes right
    734           actual = nodes (env Map.! "size")
    735       -- Constructor-K publication: the independent host count changes from
    736       -- 1845 to 1233; the compiled worker must still count its actual tree.
    737       actual @?= 1233
    738       env Map.! "main" @?= ofNumber actual
    739   , testCase "Level Order Traversal demo" $ do
    740       res     <- liftIO $ ((Map.! "main") <$> evaluateCompilerFile "./demos/levelOrderTraversal.tri")
    741       decodeResult res @?= "\"\n1 \n2 3 \n4 5 6 7 \n8 11 10 9 12 \""
    742   , testCase "Contract effect demo with do notation" $ do
    743       res     <- liftIO $ ((Map.! "main") <$> evaluateCompilerFile "./demos/contractEffects.tri")
    744       decodeResult res @?= "[t t, 10]"
    745   , testCase "Safe base wrappers demo" $ do
    746       res     <- liftIO $ ((Map.! "main") <$> evaluateCompilerFile "./demos/contractBasics.tri")
    747       decodeResult res @?= "[3, t t, t, t t]"
    748   ]
    749 
    750 decoding :: TestTree
    751 decoding = testGroup "Decoding Tests"
    752   [ testCase "Decode Leaf" $ do
    753       decodeResult Leaf @?= "t"
    754 
    755   , testCase "Decode list of non-ASCII numbers" $ do
    756       let input = ofList [ofNumber 1, ofNumber 14, ofNumber 6]
    757       decodeResult input @?= "[1, 14, 6]"
    758 
    759   , testCase "Decode list of ASCII numbers as a string" $ do
    760       let input = ofList [ofNumber 97, ofNumber 98, ofNumber 99]
    761       decodeResult input @?= "\"abc\""
    762 
    763   , testCase "Decode small number" $ do
    764       decodeResult (ofNumber 42) @?= "42"
    765 
    766   , testCase "Decode large number" $ do
    767       decodeResult (ofNumber 9999) @?= "9999"
    768 
    769   , testCase "Decode string in list" $ do
    770       let input = ofList [ofString "hello", ofString "world"]
    771       decodeResult input @?= "[\"hello\", \"world\"]"
    772 
    773   , testCase "Decode mixed list with strings" $ do
    774       let input = ofList [ofString "hello", ofNumber 42, ofString "world"]
    775       decodeResult input @?= "[\"hello\", 42, \"world\"]"
    776 
    777   , testCase "Decode nested lists with strings" $ do
    778       let input = ofList [ofList [ofString "nested"], ofString "string"]
    779       decodeResult input @?= "[[\"nested\"], \"string\"]"
    780   ]
    781 
    782 -- --------------------------------------------------------------------------
    783 -- Byte marshalling tests
    784 -- --------------------------------------------------------------------------
    785 
    786 byteMarshallingTests :: TestTree
    787 byteMarshallingTests = testGroup "Byte Marshalling Tests"
    788   [ testCase "ofByte / toByte round-trip: 0" $ do
    789       let w8 = (0 :: Word8)
    790       toByte (ofByte w8) @?= Right w8
    791 
    792   , testCase "ofByte / toByte round-trip: 1" $ do
    793       let w8 = (1 :: Word8)
    794       toByte (ofByte w8) @?= Right w8
    795 
    796   , testCase "ofByte / toByte round-trip: 127" $ do
    797       let w8 = (127 :: Word8)
    798       toByte (ofByte w8) @?= Right w8
    799 
    800   , testCase "ofByte / toByte round-trip: 128" $ do
    801       let w8 = (128 :: Word8)
    802       toByte (ofByte w8) @?= Right w8
    803 
    804   , testCase "ofByte / toByte round-trip: 255" $ do
    805       let w8 = (255 :: Word8)
    806       toByte (ofByte w8) @?= Right w8
    807 
    808   , testCase "toByte rejects value > 255" $ do
    809       -- ofNumber 256 = Fork Leaf (Fork Leaf Leaf) — value 256
    810       toByte (ofNumber 256) @?= Left "Byte value out of range: 256"
    811 
    812   , testCase "toByte accepts Leaf" $ do
    813       toByte (Leaf) @?= Right 0
    814 
    815   , testCase "toByte rejects non-number tree" $ do
    816       toByte (Stem Leaf) @?= Left "Invalid Tree Calculus number"
    817       toByte (Stem (Stem Leaf)) @?= Left "Invalid Tree Calculus number"
    818 
    819   , testCase "ofBytes / toBytes round-trip: empty ByteString" $ do
    820       toBytes (ofBytes BS.empty) @?= Right BS.empty
    821 
    822   , testCase "ofBytes / toBytes round-trip: [0x00]" $ do
    823       toBytes (ofBytes (BS.pack [0x00])) @?= Right (BS.pack [0x00])
    824 
    825   , testCase "ofBytes / toBytes round-trip: [0xff]" $ do
    826       toBytes (ofBytes (BS.pack [0xff])) @?= Right (BS.pack [0xff])
    827 
    828   , testCase "ofBytes / toBytes round-trip: mixed bytes" $ do
    829       let bytes = BS.pack [0x00, 0x01, 0x7f, 0x80, 0xff, 0x41, 0x42, 0x43]
    830       toBytes (ofBytes bytes) @?= Right bytes
    831 
    832   , testCase "toBytes rejects non-list tree" $ do
    833       -- Leaf is a valid list (empty), so this won't work.
    834       -- Stem Leaf is not a list.
    835       toBytes (Stem Leaf) @?= Left "Invalid Tree Calculus list"
    836 
    837   , testCase "toBytes rejects list containing invalid byte (>255)" $ do
    838       -- [ofNumber 256, ofNumber 1] — first element is > 255
    839       let badList = ofList [ofNumber 256, ofNumber 1]
    840       toBytes badList @?= Left "Byte value out of range: 256"
    841 
    842   , testCase "nodePayloadToTreeBytes / treeBytesToNodePayload: Leaf payload" $ do
    843       -- Leaf payload is 0x00 (1 byte)
    844       let payload = BS.pack [0x00]
    845       treeBytesToNodePayload (nodePayloadToTreeBytes payload) @?= Right payload
    846 
    847   , testCase "nodePayloadToTreeBytes / treeBytesToNodePayload: Stem payload" $ do
    848       -- Stem payload: 0x01 || 32-byte hash = 33 bytes
    849       let payload = BS.pack (0x01 : replicate 32 0x42)
    850       treeBytesToNodePayload (nodePayloadToTreeBytes payload) @?= Right payload
    851 
    852   , testCase "nodePayloadToTreeBytes / treeBytesToNodePayload: Fork payload" $ do
    853       -- Fork payload: 0x02 || 32-byte hash || 32-byte hash = 65 bytes
    854       let payload = BS.pack (0x02 : replicate 64 0x42)
    855       treeBytesToNodePayload (nodePayloadToTreeBytes payload) @?= Right payload
    856 
    857   , testCase "hashToTreeBytes / treeBytesToHash round-trip" $ do
    858       -- Use a known 32-byte hash (SHA256 of "")
    859       let hashStr :: MerkleHash
    860           hashStr = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    861       case hashToTreeBytes hashStr of
    862         Left err -> assertFailure $ "hashToTreeBytes failed: " ++ err
    863         Right tree -> treeBytesToHash tree @?= Right hashStr
    864 
    865   , testCase "hashToTreeBytes rejects invalid hex hash" $ do
    866       hashToTreeBytes "not-a-hash" @?= Left "Invalid hex MerkleHash"
    867 
    868   , testCase "hashToTreeBytes rejects non-32-byte hash" $ do
    869       -- "00" decodes to 1 byte, not 32
    870       hashToTreeBytes "00" @?= Left "Hash raw bytes must be 32 bytes"
    871 
    872   , testCase "treeBytesToHash rejects wrong byte count" $ do
    873       -- Only 16 bytes, not 32
    874       let t16 = ofBytes (BS.pack [0x41 | _ <- [1..16]])
    875       treeBytesToHash t16 @?= Left "Expected exactly 32 byte elements for hash"
    876   ]
    877 
    878 -- --------------------------------------------------------------------------
    879 -- Content store tests
    880 -- --------------------------------------------------------------------------
    881 
    882 contentStoreTests :: TestTree
    883 contentStoreTests = testGroup "Content Store Tests"
    884   [ testCase "Filesystem CAS: put/get object and sharded path" $
    885       withSystemTempDirectory "tricu-store" $ \dir -> do
    886         let store = StorePath dir
    887             domain = Domain "test.object.v1"
    888             payload = BS.pack [1, 2, 3, 4]
    889         h <- putObject store domain payload
    890         shardForHash h @?= take 3 (unpack h)
    891         objectPath store h @?= dir </> "objects" </> take 3 (unpack h) </> unpack h
    892         doesFileExist (objectPath store h) >>= (@?= True)
    893         getObject store h >>= (@?= Just payload)
    894 
    895   , testCase "Filesystem CAS: idempotent object writes" $
    896       withSystemTempDirectory "tricu-store" $ \dir -> do
    897         let store = StorePath dir
    898             domain = Domain "test.object.v1"
    899             payload = BS.pack [9, 8, 7]
    900         h1 <- putObject store domain payload
    901         h2 <- putObject store domain payload
    902         h1 @?= h2
    903         countStoredObjects store >>= (@?= 1)
    904 
    905   , testCase "Filesystem CAS: putTree/getTree round trip" $
    906       withSystemTempDirectory "tricu-store" $ \dir -> do
    907         let store = StorePath dir
    908             term = Fork (Stem Leaf) (Fork Leaf (Stem Leaf))
    909             leafHash = nodeHash NLeaf
    910             stemHash = nodeHash (NStem leafHash)
    911             rightHash = nodeHash (NFork leafHash stemHash)
    912             expectedRoot = nodeHash (NFork stemHash rightHash)
    913         root <- putTree store term
    914         root @?= expectedRoot
    915         getTree store root >>= (@?= Just term)
    916 
    917   , testCase "Filesystem CAS: shared subtrees are deduplicated" $
    918       withSystemTempDirectory "tricu-store" $ \dir -> do
    919         let store = StorePath dir
    920             shared = Stem Leaf
    921             term = Fork shared shared
    922         _ <- putTree store term
    923         countStoredObjects store >>= (@?= 3)
    924 
    925   , testCase "Workspace aliases: write/read/list object refs" $
    926       withSystemTempDirectory "tricu-store" $ \dir -> do
    927         let store = StorePath dir
    928             ref = ObjectRef "arboricx.tree-root.v1" "abc123"
    929         writeAlias store NameAlias "main" ref
    930         readAlias store NameAlias "main" >>= (@?= Just ref)
    931         listAliases store NameAlias >>= (@?= [("main", ref)])
    932 
    933   , testCase "Module manifests: put/get round trip through CAS" $
    934       withSystemTempDirectory "tricu-store" $ \dir -> do
    935         let store = StorePath dir
    936             term = Fork Leaf (Stem Leaf)
    937             manifestFor root = ModuleManifest []
    938               [ ModuleExport
    939                   "main"
    940                   (ObjectRef (unDomain treeTermDomain) root)
    941                   "arboricx.abi.tree.v1"
    942               ]
    943         root <- putTreeTerm store term
    944         h <- putManifest store (manifestFor root)
    945         getManifest store h >>= (@?= Just (manifestFor root))
    946 
    947   , testCase "ObjectResolver: resolves manifests and trees without filesystem coupling" $ do
    948       let term = Fork Leaf (Stem Leaf)
    949           leafH = nodeHash NLeaf
    950           stemH = nodeHash (NStem leafH)
    951           rootH = nodeHash (NFork leafH stemH)
    952           termH = hashObject treeTermDomain (encodeTreeTerm term)
    953           manifest = ModuleManifest []
    954             [ ModuleExport
    955                 "value"
    956                 (ObjectRef (unDomain treeTermDomain) termH)
    957                 "arboricx.abi.tree.v1"
    958               ]
    959           manifestBytes = encodeManifest manifest
    960           manifestH = hashObject manifestDomain manifestBytes
    961           objects = Map.fromList
    962             [ (("arboricx.merkle.node.v1", leafH), serializeNode NLeaf)
    963             , (("arboricx.merkle.node.v1", stemH), serializeNode (NStem leafH))
    964             , (("arboricx.merkle.node.v1", rootH), serializeNode (NFork leafH stemH))
    965             , ((unDomain treeTermDomain, termH), encodeTreeTerm term)
    966             , ((unDomain manifestDomain, manifestH), manifestBytes)
    967             ]
    968           hydrate objs h = case deserializeNode <$> Map.lookup ("arboricx.merkle.node.v1", h) objs of
    969             Nothing -> return Nothing
    970             Just NLeaf -> return (Just Leaf)
    971             Just (NStem child) -> fmap Stem <$> hydrate objs child
    972             Just (NFork left right) -> do
    973               l <- hydrate objs left
    974               r <- hydrate objs right
    975               return $ Fork <$> l <*> r
    976           resolver = ObjectResolver
    977             { resolverAlias = \kind name -> return $ if kind == ModuleAlias && name == "demo"
    978                 then Just (ObjectRef (unDomain manifestDomain) manifestH)
    979                 else Nothing
    980             , resolverObject = \ref -> return $ Map.lookup (objectRefKind ref, objectRefHash ref) objects
    981             , resolverManifest = \h -> return $ do
    982                 bytes <- Map.lookup (unDomain manifestDomain, h) objects
    983                 either (const Nothing) Just (decodeManifest bytes)
    984             , resolverTree = hydrate objects
    985             }
    986       resolverAlias resolver ModuleAlias "demo" >>= (@?= Just (ObjectRef (unDomain manifestDomain) manifestH))
    987       resolveManifest resolver manifestH >>= (@?= Just manifest)
    988       resolveTree resolver rootH >>= (@?= Just term)
    989 
    990   , testCase "Arboricx bundle: unpack transport bundle into CAS tree terms" $
    991       withSystemTempDirectory "tricu-store" $ \dir -> do
    992         let store = StorePath dir
    993             term = Fork (Stem Leaf) Leaf
    994             bundle = buildBundle [("main", term)]
    995         exports <- unpackBundleToStore store (encodeBundle bundle)
    996         case exports of
    997           [("main", root)] -> getTreeTerm store root >>= (@?= Just term)
    998           other -> assertFailure $ "unexpected exports: " ++ show other
    999 
   1000   , testCase "Indexed CAS: extract roots without labels or unrelated nodes affecting identity" $ do
   1001       let term = Fork (Stem Leaf) (Stem Leaf)
   1002           first = buildBundle [("alpha", term)]
   1003           second = buildBundle [("unrelated", ofNumber 99), ("beta", term)]
   1004           [ex] = manifestExports (bundleManifest first)
   1005           [_, other] = manifestExports (bundleManifest second)
   1006       a <- either assertFailure pure (extractBundleRoot first $ exportRoot ex)
   1007       b <- either assertFailure pure (extractBundleRoot second $ exportRoot other)
   1008       encodeBundle a @?= encodeBundle b
   1009       decodeIndexedTree (encodeBundle a) @?= Right term
   1010       hashObject indexedTreeDomain (encodeBundle a) /= hashObject treeTermDomain (encodeTreeTerm term) @? "distinct object identities"
   1011 
   1012   , testCase "Indexed CAS: exponentially shared logical tree stays an indexed graph" $ do
   1013       let base = buildBundle [("unused", Leaf)]
   1014           graph = base { bundleNodes = Seq.fromList
   1015             (BNLeaf : [BNFork (i - 1) (i - 1) | i <- [1..80]]) }
   1016       root <- either assertFailure pure (extractBundleRoot graph 80)
   1017       Seq.length (bundleNodes root) @?= 81
   1018       BS.length (encodeBundle root) < 2048 @? "graph storage must not expand 2^81-1 logical nodes"
   1019       case decodeIndexedTree (encodeBundle root) of
   1020         Right (Fork _ _) -> pure ()
   1021         other -> assertFailure $ "expected shared fork, got " ++ either id (const "non-fork") other
   1022 
   1023   , testCase "Indexed CAS: multiple exports become independently loadable objects" $
   1024       withSystemTempDirectory "tricu-indexed-store" $ \dir -> do
   1025         let store = StorePath dir
   1026             terms = [("left", ofNumber 23), ("right", ofNumber 24)]
   1027         roots <- unpackIndexedBundleToStore store (encodeBundle $ buildBundle terms)
   1028         map fst roots @?= map fst terms
   1029         forM_ (zip roots terms) $ \((_, digest), (_, term)) -> do
   1030           payload <- getObject store digest >>= maybe (assertFailure "missing indexed object" >> pure BS.empty) pure
   1031           hashObject indexedTreeDomain payload @?= digest
   1032           decodeIndexedTree payload @?= Right term
   1033 
   1034   , testCase "Indexed CAS: reject malformed graphs and ambiguous root containers" $ do
   1035       let leaf = buildBundle [("root", Leaf)]
   1036           forward = leaf { bundleNodes = Seq.singleton (BNStem 0) }
   1037           multiple = buildBundle [("a", Leaf), ("b", Stem Leaf)]
   1038       case extractBundleRoot forward 0 of
   1039         Left _ -> pure ()
   1040         Right _ -> assertFailure "accepted cyclic node"
   1041       case extractBundleRoot leaf 1 of
   1042         Left _ -> pure ()
   1043         Right _ -> assertFailure "accepted out-of-range root"
   1044       case decodeIndexedTree (encodeBundle multiple) of
   1045         Left _ -> pure ()
   1046         Right _ -> assertFailure "accepted multiple root container"
   1047 
   1048   , testCase "Streaming pack: exact legacy bundle bytes across chunk boundaries" $ do
   1049       let terms = [(pack ("value" ++ show n), ofNumber n) | n <- [0..128]] ++
   1050                   [("alias", ofNumber 97), ("tree", Fork (Stem Leaf) (Fork Leaf Leaf))]
   1051           packed chunkSize = do
   1052             builder <- foldM (appendTerm chunkSize) emptyTreeTermBundle terms
   1053             pure (finishTreeTermBundle builder)
   1054           appendTerm chunkSize builder (name, term) = do
   1055             let chunks bytes | BS.null bytes = []
   1056                              | otherwise = let (first, rest) = BS.splitAt chunkSize bytes
   1057                                            in first : chunks rest
   1058             cursor <- foldM (flip feedTreeTerm) (startTreeTerm builder) (chunks (encodeTreeTerm term))
   1059             endTreeTerm name cursor
   1060       forM_ [1, 2, 3, 7, 65536] $ \chunkSize -> do
   1061         actual <- either assertFailure pure (packed chunkSize)
   1062         encodeBundle actual @?= encodeBundle (buildBundle terms)
   1063         verifyBundle actual @?= Right ()
   1064       encodeBundle (finishTreeTermBundle emptyTreeTermBundle) @?= encodeBundle (buildBundle [])
   1065 
   1066   , testCase "Streaming pack: rejects truncated, unknown and trailing tree bytes" $ do
   1067       let parse bytes = feedTreeTerm bytes (startTreeTerm emptyTreeTermBundle) >>= endTreeTerm "main"
   1068           failure bytes expected = case parse bytes of
   1069             Left problem -> problem @?= expected
   1070             Right _ -> assertFailure "accepted malformed canonical tree"
   1071       forM_ [[], [1], [2,0], [2,1,0]] $ \bytes ->
   1072         failure (BS.pack bytes) "unexpected end of tree term"
   1073       failure (BS.pack [3]) "unknown tree term tag: 3"
   1074       failure (BS.pack [0,0]) "trailing bytes after tree term"
   1075       failure (BS.pack [1,0,3]) "trailing bytes after tree term"
   1076 
   1077   , testCase "Streaming pack: deep input does not need recursive tree construction" $ do
   1078       let depth = 16384
   1079       cursor <- either assertFailure pure $
   1080         feedTreeTerm (BS.replicate depth 1 <> BS.singleton 0) (startTreeTerm emptyTreeTermBundle)
   1081       builder <- either assertFailure pure (endTreeTerm "main" cursor)
   1082       let bundle = finishTreeTermBundle builder
   1083       Seq.length (bundleNodes bundle) @?= depth + 1
   1084       bundleRoots bundle @?= [fromIntegral depth]
   1085       verifyBundle bundle @?= Right ()
   1086 
   1087   , testCase "Streaming pack: CAS digest verification and byte-equivalent shared exports" $
   1088       withSystemTempDirectory "tricu-streaming-pack" $ \dir -> do
   1089         let store = StorePath dir
   1090             terms = [("a", Fork (Stem Leaf) (Stem Leaf)), ("b", Stem Leaf),
   1091                      ("alias", Fork (Stem Leaf) (Stem Leaf))]
   1092         roots <- forM terms $ \(name, term) -> (,) name <$> putTreeTerm store term
   1093         bundle <- packBundleFromStore store roots
   1094         encodeBundle bundle @?= encodeBundle (buildBundle terms)
   1095         -- Cross the actual file reader's 64 KiB boundary with unfinished frames.
   1096         let depth = 65537
   1097         deep <- putObject store treeTermDomain (BS.replicate depth 1 <> BS.singleton 0)
   1098         deepBundle <- packBundleFromStore store [("deep", deep), ("alias", deep)]
   1099         Seq.length (bundleNodes deepBundle) @?= depth + 1
   1100         bundleRoots deepBundle @?= replicate 2 (fromIntegral depth)
   1101         verifyBundle deepBundle @?= Right ()
   1102         let root = snd (head roots)
   1103         BS.writeFile (objectPath store root) (encodeTreeTerm Leaf)
   1104         outcome <- try (packBundleFromStore store roots) :: IO (Either SomeException Bundle)
   1105         case outcome of
   1106           Left problem -> show problem `containsAll` ["Executable tree digest mismatch", unpack root]
   1107           Right _ -> assertFailure "packed corrupted CAS bytes"
   1108         forM_ [BS.empty, BS.pack [2,0], BS.pack [3], BS.pack [0,0]] $ \bytes -> do
   1109           malformed <- putObject store treeTermDomain bytes
   1110           rejected <- try (packBundleFromStore store [("bad", malformed)]) :: IO (Either SomeException Bundle)
   1111           case rejected of
   1112             Left _ -> pure ()
   1113             Right _ -> assertFailure "packed malformed but correctly hashed CAS bytes"
   1114 
   1115   , testCase "Arboricx bundle: pack CAS tree terms into transport bundle" $
   1116       withSystemTempDirectory "tricu-store" $ \dir -> do
   1117         let store = StorePath dir
   1118             term = Fork Leaf (Stem Leaf)
   1119         root <- putTreeTerm store term
   1120         bundle <- packBundleFromStore store [("main", root)]
   1121         bundleRoots bundle @?= [2]
   1122         let terms = reconstructBundleTermsForTest (bundleNodes bundle)
   1123         case manifestExports (bundleManifest bundle) of
   1124           [exported] -> do
   1125             exportName exported @?= "main"
   1126             terms V.! fromIntegral (exportRoot exported) @?= term
   1127           other -> assertFailure $ "unexpected exports: " ++ show other
   1128   ]
   1129 
   1130 reconstructBundleTermsForTest :: Seq.Seq BundleNode -> V.Vector T
   1131 reconstructBundleTermsForTest nodes = V.fromList (go <$> Foldable.toList nodes)
   1132   where
   1133     built = V.fromList (go <$> Foldable.toList nodes)
   1134     go BNLeaf = Leaf
   1135     go (BNStem child) = Stem (built V.! fromIntegral child)
   1136     go (BNFork left right) = Fork (built V.! fromIntegral left) (built V.! fromIntegral right)
   1137 
   1138 countStoredObjects :: StorePath -> IO Int
   1139 countStoredObjects store@(StorePath root) = do
   1140   ensureStore store
   1141   shards <- listDirectory (root </> "objects")
   1142   fmap sum $ forM shards $ \shard -> do
   1143     files <- listDirectory (root </> "objects" </> shard)
   1144     return (length files)
   1145 
   1146 -- --------------------------------------------------------------------------
   1147 -- Wire module tests
   1148 -- --------------------------------------------------------------------------
   1149 
   1150 -- | Helper: create a temporary file-backed DB, store a term, return the
   1151 
   1152 wireTests :: TestTree
   1153 wireTests = testGroup "Wire Tests"
   1154   [ testCase "Indexed bundle: header and manifest declare indexed format" $ do
   1155       let term = Leaf
   1156           bundle = buildBundle [("main", term)]
   1157           wireData = encodeBundle bundle
   1158       BS.take 8 wireData @?= BS.pack [0x41, 0x52, 0x42, 0x4f, 0x52, 0x49, 0x43, 0x58]
   1159       case decodeBundle wireData of
   1160         Left err -> assertFailure $ "decodeBundle failed: " ++ err
   1161         Right decoded -> do
   1162           let manifest = bundleManifest decoded
   1163               tree = manifestTree manifest
   1164               hashSpec = treeNodeHash tree
   1165           manifestSchema manifest @?= "arboricx.bundle.manifest.v1"
   1166           manifestBundleType manifest @?= "tree-calculus-executable-object"
   1167           manifestClosure manifest @?= ClosureComplete
   1168           treeCalculus tree @?= "tree-calculus.v1"
   1169           treeNodePayload tree @?= "arboricx.indexed.payload.v1"
   1170           nodeHashAlgorithm hashSpec @?= "indexed"
   1171           nodeHashDomain hashSpec @?= "arboricx.indexed.node.v1"
   1172           bundleRoots decoded @?= bundleRoots bundle
   1173           case manifestExports manifest of
   1174             [exported] -> do
   1175               exportName exported @?= "main"
   1176               exportRoot exported @?= head (bundleRoots bundle)
   1177               exportKind exported @?= "term"
   1178               exportAbi exported @?= "arboricx.abi.tree.v1"
   1179             exports -> assertFailure $ "Expected one export, got: " ++ show exports
   1180 
   1181   , testCase "Indexed bundle: deterministic encoding" $ do
   1182       let term = Stem (Stem Leaf)
   1183           bundle1 = buildBundle [("main", term)]
   1184           bundle2 = buildBundle [("main", term)]
   1185       encodeBundle bundle1 @?= encodeBundle bundle2
   1186 
   1187   , testCase "Indexed bundle: renaming export changes bytes" $ do
   1188       let term = Leaf
   1189           mainBundle = buildBundle [("main", term)]
   1190           renamedBundle = buildBundle [("validate", term)]
   1191       encodeBundle mainBundle /= encodeBundle renamedBundle @? "different export names should produce different bytes"
   1192       -- But nodes are identical
   1193       bundleNodes mainBundle @?= bundleNodes renamedBundle
   1194 
   1195   , testCase "Indexed bundle: verify rejects out-of-bounds root" $ do
   1196       let term = Leaf
   1197           bundle = buildBundle [("main", term)]
   1198           badBundle = bundle { bundleRoots = [99] }
   1199       case verifyBundle badBundle of
   1200         Left err -> assertBool ("Expected bounds error, got: " ++ err) ("out of bounds" `isInfixOf` err)
   1201         Right () -> assertFailure "Expected out-of-bounds root to be rejected"
   1202 
   1203   , testCase "Indexed bundle: verify rejects out-of-bounds child index" $ do
   1204       let bundle = Bundle
   1205             { bundleVersion = 1000
   1206             , bundleRoots = [1]
   1207             , bundleNodes = Seq.fromList [BNLeaf, BNStem 99]
   1208             , bundleManifest = (bundleManifest $ buildBundle [("main", Leaf)])
   1209                 { manifestRoots = [BundleRoot 1 "default"]
   1210                 , manifestExports = [BundleExport "main" 1 "term" "arboricx.abi.tree.v1"]
   1211                 }
   1212             , bundleManifestBytes = BS.empty
   1213             }
   1214       case verifyBundle bundle of
   1215         Left err -> assertBool ("Expected bounds error, got: " ++ err) ("references child 99" `isInfixOf` err)
   1216         Right () -> assertFailure "Expected out-of-bounds child to be rejected"
   1217 
   1218   , testCase "Indexed bundle: verify rejects acyclic (forward reference)" $ do
   1219       let bundle = Bundle
   1220             { bundleVersion = 1000
   1221             , bundleRoots = [1]
   1222             , bundleNodes = Seq.fromList [BNStem 1, BNLeaf]  -- index 0 refers to 1 (forward)
   1223             , bundleManifest = (bundleManifest $ buildBundle [("main", Leaf)])
   1224                 { manifestRoots = [BundleRoot 1 "default"]
   1225                 , manifestExports = [BundleExport "main" 1 "term" "arboricx.abi.tree.v1"]
   1226                 }
   1227             , bundleManifestBytes = BS.empty
   1228             }
   1229       case verifyBundle bundle of
   1230         Left err -> assertBool ("Expected acyclicity error, got: " ++ err) ("references child 1" `isInfixOf` err)
   1231         Right () -> assertFailure "Expected forward reference to be rejected"
   1232 
   1233   , testCase "Indexed bundle: verify rejects duplicate nodes" $ do
   1234       let bundle = Bundle
   1235             { bundleVersion = 1000
   1236             , bundleRoots = [0]
   1237             , bundleNodes = Seq.fromList [BNLeaf, BNLeaf]
   1238             , bundleManifest = (bundleManifest $ buildBundle [("main", Leaf)])
   1239                 { manifestRoots = [BundleRoot 0 "default"]
   1240                 , manifestExports = [BundleExport "main" 0 "term" "arboricx.abi.tree.v1"]
   1241                 }
   1242             , bundleManifestBytes = BS.empty
   1243             }
   1244       case verifyBundle bundle of
   1245         Left err -> assertBool ("Expected duplicate error, got: " ++ err) ("duplicate" `isInfixOf` err)
   1246         Right () -> assertFailure "Expected duplicate nodes to be rejected"
   1247 
   1248   , testCase "Indexed bundle: unpack into filesystem CAS" $
   1249       withSystemTempDirectory "tricu-store" $ \dir -> do
   1250         let term = Leaf
   1251             bundle = buildBundle [("validateEmail", term)]
   1252             wireData = encodeBundle bundle
   1253             store = StorePath dir
   1254         roots <- unpackBundleToStore store wireData
   1255         case roots of
   1256           [("validateEmail", root)] -> getTreeTerm store root >>= (@?= Just term)
   1257           other -> assertFailure $ "unexpected roots: " ++ show other
   1258 
   1259   , testCase "Indexed bundle: round-trip decode and verify" $ do
   1260       let term = Stem (Stem Leaf)
   1261           bundle = buildBundle [("main", term)]
   1262           wireData = encodeBundle bundle
   1263       case decodeBundle wireData of
   1264         Left err -> assertFailure $ "decodeBundle failed: " ++ err
   1265         Right decoded -> case verifyBundle decoded of
   1266           Left err -> assertFailure $ "verifyBundle failed: " ++ err
   1267           Right () -> do
   1268             bundleRoots decoded @?= bundleRoots bundle
   1269             Seq.length (bundleNodes decoded) @?= Seq.length (bundleNodes bundle)
   1270 
   1271   , testCase "Indexed bundle: unsupported manifest semantics rejected" $ do
   1272       let term = Leaf
   1273           bundle = buildBundle [("main", term)]
   1274           manifest = bundleManifest bundle
   1275           partialBundle = bundle
   1276             { bundleManifest = manifest { manifestClosure = ClosurePartial }
   1277             , bundleManifestBytes = BS.empty
   1278             }
   1279           capabilityBundle = bundle
   1280             { bundleManifest = manifest
   1281                 { manifestRuntime = (manifestRuntime manifest)
   1282                     { runtimeCapabilities = ["host.io"] }
   1283                 }
   1284             , bundleManifestBytes = BS.empty
   1285             }
   1286           wrongHashBundle = bundle
   1287             { bundleManifest = manifest
   1288                 { manifestTree = (manifestTree manifest)
   1289                     { treeNodeHash = (treeNodeHash $ manifestTree manifest)
   1290                         { nodeHashAlgorithm = "blake3" }
   1291                     }
   1292                 }
   1293             , bundleManifestBytes = BS.empty
   1294             }
   1295       case verifyBundle partialBundle of
   1296         Left err -> assertBool ("Expected closure error, got: " ++ err) ("closure = complete" `isInfixOf` err)
   1297         Right () -> assertFailure "Expected partial closure to be rejected"
   1298       case verifyBundle capabilityBundle of
   1299         Left err -> assertBool ("Expected capability error, got: " ++ err) ("capabilities" `isInfixOf` err)
   1300         Right () -> assertFailure "Expected runtime capabilities to be rejected"
   1301       case verifyBundle wrongHashBundle of
   1302         Left err -> assertBool ("Expected hash algorithm error, got: " ++ err) ("node hash algorithm" `isInfixOf` err)
   1303         Right () -> assertFailure "Expected unsupported node hash algorithm to be rejected"
   1304     ]
   1305 
   1306 -- --------------------------------------------------------------------------
   1307 -- Tricu reader tests
   1308 -- Smoke-test the tricu-native Arboricx reader against indexed bundles.
   1309 -- --------------------------------------------------------------------------
   1310 
   1311 tricuReaderTests :: TestTree
   1312 tricuReaderTests = testGroup "Tricu Reader Tests"
   1313   [ testCase "Tricu reader parses indexed bundle (id fixture)" $ check "id"
   1314   , testCase "Tricu reader parses indexed bundle (append fixture)" $ check "append"
   1315   , testCase "Tricu reader parses indexed bundle (bool fixtures)" $ mapM_ check ["true", "false"]
   1316   ]
   1317   where
   1318     check name = do
   1319       bytes <- BS.readFile ("./test/fixtures/" ++ name ++ ".arboricx")
   1320       let reply = foldl apply (fixtureExports "arboricx" Map.! "runArboricx") [ofBytes bytes, Leaf]
   1321       apply (fixtureExports "base" Map.! "fst") reply @?= trueT
   1322 
   1323 -- --------------------------------------------------------------------------
   1324 -- Byte-list utility tests
   1325 -- Expected values built with canonical Haskell-side T constructors.
   1326 -- --------------------------------------------------------------------------
   1327 
   1328 -- | Helpers for byte-list test expectations.
   1329 
   1330 trueT  :: T
   1331 trueT  = Stem Leaf
   1332 
   1333 falseT :: T
   1334 falseT = Leaf
   1335 
   1336 pairT :: T -> T -> T
   1337 pairT = Fork
   1338 
   1339 portableReply :: Bool -> T -> T -> Bool -> Bool -> T
   1340 portableReply success payload rest consumed committed =
   1341   pairT (if success then trueT else falseT)
   1342     (pairT payload (pairT rest (pairT
   1343       (if consumed then trueT else falseT)
   1344       (if committed then trueT else falseT))))
   1345 
   1346 portableDiagnostic :: T -> T -> T -> T -> T -> T
   1347 portableDiagnostic code expected found location contexts =
   1348   pairT code (pairT expected (pairT found (pairT location contexts)))
   1349 
   1350 -- --------------------------------------------------------------------------
   1351 -- Reusable, located character-scanning primitives
   1352 -- --------------------------------------------------------------------------
   1353 
   1354 locatedLexerTests :: TestTree
   1355 locatedLexerTests = testGroup "Located character-scanning library"
   1356   [ testCase "checked native scanner handles abstract input" $ do
   1357         env <- evaluateCompilerFile "./test/lexerScanImport.tri"
   1358         case Map.lookup "scanner" env of
   1359           Nothing -> assertFailure "missing portable scanner"
   1360           Just scanner -> do
   1361             apply scanner (ofString "aa!") @?=
   1362               portableReply True
   1363                 (pairT (ofList [ofNumber 97, ofNumber 97])
   1364                   (gatePosition 1 3 2))
   1365                 (ofString "!") True False
   1366             apply scanner Leaf @?=
   1367               portableReply True (pairT Leaf (gatePosition 1 1 0)) Leaf False False
   1368             apply scanner (ofString "a\nx") @?=
   1369               portableReply True
   1370                 (pairT (ofList [ofNumber 97]) (gatePosition 1 2 1))
   1371                 (ofString "\nx") True False
   1372         case Map.lookup "dispatch" env of
   1373           Nothing -> assertFailure "missing ordered dispatch"
   1374           Just dispatch -> do
   1375             apply dispatch (ofNumber 97) @?= ofNumber 1
   1376             apply dispatch (ofNumber 98) @?= ofNumber 3
   1377   ]
   1378 
   1379 -- --------------------------------------------------------------------------
   1380 -- Single-expression feasibility-gate source lexer
   1381 -- --------------------------------------------------------------------------
   1382 
   1383 gateLexerEnv :: Env
   1384 gateLexerEnv = fixtureExports "source.lexer"
   1385 {-# NOINLINE gateLexerEnv #-}
   1386 
   1387 gatePosition :: Integer -> Integer -> Integer -> T
   1388 gatePosition line column offset =
   1389   pairT (ofNumber line) (pairT (ofNumber column) (ofNumber offset))
   1390 
   1391 gateToken :: Integer -> T -> T -> T -> T
   1392 gateToken kind payload start end =
   1393   pairT (ofNumber kind) (pairT payload (pairT start end))
   1394 
   1395 gateLexerTests :: TestTree
   1396 gateLexerTests = testGroup "Feasibility-gate source lexer"
   1397   [ testCase "module export normalizes under abstract source input" $
   1398       withSystemTempDirectory "gate-lexer-import" $ \dir -> do
   1399         env <- evaluateCompilerFile "./test/sourceLexerImport.tri"
   1400         Map.lookup "main" env @?= Just Leaf
   1401   , testCase "keyword, identifiers, and punctuation carry spans" $ do
   1402       check "t (foo : turtle)"
   1403         (portableReply True
   1404           (ofList
   1405             [ gateToken 1 (ofString "t") (gatePosition 1 1 0) (gatePosition 1 2 1)
   1406             , gateToken 3 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3)
   1407             , gateToken 2 (ofString "foo") (gatePosition 1 4 3) (gatePosition 1 7 6)
   1408             , gateToken 5 Leaf (gatePosition 1 8 7) (gatePosition 1 9 8)
   1409             , gateToken 2 (ofString "turtle") (gatePosition 1 10 9) (gatePosition 1 16 15)
   1410             , gateToken 4 Leaf (gatePosition 1 16 15) (gatePosition 1 17 16)
   1411             ]) Leaf True False)
   1412   , testCase "decimal digits accumulate and retain leading-zero span" $ do
   1413       check "009 x" (portableReply True
   1414         (ofList
   1415           [ gateToken 7 (ofNumber 9) (gatePosition 1 1 0) (gatePosition 1 4 3)
   1416           , gateToken 2 (ofString "x") (gatePosition 1 5 4) (gatePosition 1 6 5)
   1417           ]) Leaf True False)
   1418   , testCase "quoted string decodes escapes and retains raw source span" $ do
   1419       let input = "\"a\\n\\\"b\""
   1420       check input (portableReply True
   1421         (ofList [gateToken 8 (ofString "a\n\"b")
   1422           (gatePosition 1 1 0) (gatePosition 1 9 8)]) Leaf True False)
   1423   , testCase "source string escape repertoire is preserved" $ do
   1424       let input = "\"\\t\\r\\f\\b\\\\\\'\""
   1425           decoded = "\t\r\f\b\\'"
   1426           width = fromIntegral (length input)
   1427       check input (portableReply True
   1428         (ofList [gateToken 8 (ofString decoded)
   1429           (gatePosition 1 1 0) (gatePosition 1 (width + 1) width)])
   1430         Leaf True False)
   1431   , testCase "bracket punctuation retains both half-open spans" $ do
   1432       check "[t]" (portableReply True
   1433         (ofList
   1434           [ gateToken 9 Leaf (gatePosition 1 1 0) (gatePosition 1 2 1)
   1435           , gateToken 1 (ofString "t") (gatePosition 1 2 1) (gatePosition 1 3 2)
   1436           , gateToken 10 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3)
   1437           ]) Leaf True False)
   1438   , testCase "equation commas are located tokens, not expression-list syntax" $
   1439       check "[t, t]" (portableReply True (ofList
   1440         [ gateToken 9 Leaf (gatePosition 1 1 0) (gatePosition 1 2 1)
   1441         , gateToken 1 (ofString "t") (gatePosition 1 2 1) (gatePosition 1 3 2)
   1442         , gateToken 20 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3)
   1443         , gateToken 1 (ofString "t") (gatePosition 1 5 4) (gatePosition 1 6 5)
   1444         , gateToken 10 Leaf (gatePosition 1 6 5) (gatePosition 1 7 6)
   1445         ]) Leaf True False)
   1446   , testCase "comment delimiters inside strings remain literal" $
   1447       check "\"--|-\"" (portableReply True
   1448         (ofList [gateToken 8 (ofString "--|-")
   1449           (gatePosition 1 1 0) (gatePosition 1 7 6)]) Leaf True False)
   1450   , testCase "invalid and unfinished string escapes return diagnostics" $ do
   1451       check "\"\\q\"" (portableReply False
   1452         (portableDiagnostic (ofNumber 6) (ofNumber 34) (ofNumber 113)
   1453           (gatePosition 1 3 2) Leaf) (ofString "\\q\"") True False)
   1454       check "\"hi" (portableReply False
   1455         (portableDiagnostic (ofNumber 5) (ofNumber 34) Leaf
   1456           (gatePosition 1 1 0) Leaf) Leaf True False)
   1457   , testCase "line comment preserves newline token and locations" $ do
   1458       check "t --hello\nx" (portableReply True
   1459         (ofList
   1460           [ gateToken 1 (ofString "t") (gatePosition 1 1 0) (gatePosition 1 2 1)
   1461           , gateToken 6 Leaf (gatePosition 1 10 9) (gatePosition 2 1 10)
   1462           , gateToken 2 (ofString "x") (gatePosition 2 1 10) (gatePosition 2 2 11)
   1463           ]) Leaf True False)
   1464   , testCase "block comment consumes internal newlines but preserves end span" $ do
   1465       let input = "t |-ab\nc-| t"
   1466       check input (portableReply True
   1467         (ofList
   1468           [ gateToken 1 (ofString "t") (gatePosition 1 1 0) (gatePosition 1 2 1)
   1469           , gateToken 1 (ofString "t") (gatePosition 2 5 11) (gatePosition 2 6 12)
   1470           ]) Leaf True False)
   1471   , testCase "empty block comment consumes both delimiters" $
   1472       check "|- -|t" (portableReply True
   1473         (ofList [gateToken 1 (ofString "t")
   1474           (gatePosition 1 6 5) (gatePosition 1 7 6)]) Leaf True False)
   1475   , testCase "unterminated block comment returns a structured diagnostic" $
   1476       check "t |-oops" (portableReply False
   1477         (portableDiagnostic (ofNumber 5) (ofString "-|") Leaf
   1478           (gatePosition 1 3 2) Leaf) Leaf True False)
   1479   , testCase "comment at EOF leaves no phantom token" $
   1480       check "t --bye" (portableReply True
   1481         (ofList [gateToken 1 (ofString "t")
   1482           (gatePosition 1 1 0) (gatePosition 1 2 1)]) Leaf True False)
   1483   , testCase "lone dash remains a located lexical error" $
   1484       check "-" (portableReply False
   1485         (portableDiagnostic (ofNumber 4) Leaf (ofNumber 45)
   1486           (gatePosition 1 1 0) Leaf) (ofString "-") False False)
   1487   , testCase "newline advances source position" $ do
   1488       check "t\nfoo"
   1489         (portableReply True
   1490           (ofList
   1491             [ gateToken 1 (ofString "t") (gatePosition 1 1 0) (gatePosition 1 2 1)
   1492             , gateToken 6 Leaf (gatePosition 1 2 1) (gatePosition 2 1 2)
   1493             , gateToken 2 (ofString "foo") (gatePosition 2 1 2) (gatePosition 2 4 5)
   1494             ]) Leaf True False)
   1495   , testCase "invalid character returns structured diagnostic at actual input" $
   1496       check "t #"
   1497         (portableReply False
   1498           (portableDiagnostic (ofNumber 4) Leaf (ofNumber 35)
   1499             (gatePosition 1 3 2) Leaf) (ofString "#") True False)
   1500   , testCase "explicit scalar BOM is rejected, matching browser character input" $
   1501       apply (gateLexerEnv Map.! "lexGateSource") (ofString "\xfeff\&t") @?=
   1502         portableReply False
   1503           (portableDiagnostic (ofNumber 4) Leaf (ofNumber 65279)
   1504             (gatePosition 1 1 0) Leaf) (ofString "\xfeff\&t") False False
   1505   , testCase "empty input yields empty tokens and no consumption" $
   1506       check ""
   1507         (portableReply True Leaf Leaf False False)
   1508   , testCase "tab is rejected at its source position" $
   1509       check "t\tfoo"
   1510         (portableReply False
   1511           (portableDiagnostic (ofNumber 4) Leaf (ofNumber 9)
   1512             (gatePosition 1 2 1) Leaf) (ofString "\tfoo") True False)
   1513   , testCase "pipe operators retain token kinds and two-character spans" $ do
   1514       check "<| |>" (portableReply True
   1515         (ofList
   1516           [ gateToken 15 Leaf (gatePosition 1 1 0) (gatePosition 1 3 2)
   1517           , gateToken 16 Leaf (gatePosition 1 4 3) (gatePosition 1 6 5)
   1518           ]) Leaf True False)
   1519   , testCase "do and bind arrow have independent located token kinds" $ do
   1520       check "do <-" (portableReply True
   1521         (ofList
   1522           [ gateToken 17 (ofString "do")
   1523               (gatePosition 1 1 0) (gatePosition 1 3 2)
   1524           , gateToken 18 Leaf (gatePosition 1 4 3) (gatePosition 1 6 5)
   1525           ]) Leaf True False)
   1526   , testCase "ASCII letter/digit range boundaries remain recognized" $ do
   1527       check "AZaz09" (portableReply True
   1528         (ofList [gateToken 2 (ofString "AZaz09")
   1529           (gatePosition 1 1 0) (gatePosition 1 7 6)]) Leaf True False)
   1530   , testCase "longer ASCII identifier scans without discarding suffix" $
   1531       let name = replicate 32 'a'
   1532       in check (name ++ " t")
   1533         (portableReply True
   1534           (ofList
   1535             [ gateToken 2 (ofString name) (gatePosition 1 1 0) (gatePosition 1 33 32)
   1536             , gateToken 1 (ofString "t") (gatePosition 1 34 33) (gatePosition 1 35 34)
   1537             ]) Leaf True False)
   1538   ]
   1539   where
   1540     check input expected =
   1541       apply (gateLexerEnv Map.! "lexGateSource") (ofString input) @?= expected
   1542 
   1543 -- --------------------------------------------------------------------------
   1544 -- Single-expression feasibility-gate surface parser
   1545 -- --------------------------------------------------------------------------
   1546 
   1547 -- Library paths select authenticated published interfaces. Unmapped test units
   1548 -- use the portable compiler; neither path interprets historical frontend syntax.
   1549 evaluateCompilerFile :: FilePath -> IO Env
   1550 evaluateCompilerFile path =
   1551   case [legacy | (legacy, _, source) <- fst compiledLibraries,
   1552                source == maybe path id (stripPrefix "./" path)] of
   1553     [legacy] -> pure (fixtureExports legacy)
   1554     [] -> compileFixtureFile path
   1555     _ -> error "ambiguous compiler fixture module"
   1556 
   1557 -- Test wrappers are compiled by the retained portable stable unit API; library
   1558 -- payloads come from authenticated fixtures. Packing remains a host mechanism.
   1559 packCompilerFile :: FilePath -> FilePath -> [Text] -> IO ()
   1560 packCompilerFile input output names = do
   1561   env <- evaluateCompilerFile input
   1562   let selected = [(name, env Map.! unpack name) | name <- names]
   1563   BS.writeFile output (encodeBundle (buildBundle selected))
   1564 
   1565 -- Immutable pre-optimization worker from the accepted d7b3c37 generation.
   1566 -- No source compilation or fresh module builds enter the fast gate. Keeping
   1567 -- this separate from the current compiler fixture makes fallback comparisons
   1568 -- independent of the implementation under test.
   1569 hashHexReference :: Env
   1570 hashHexReference = unsafePerformIO $ do
   1571   bytes <- BS.readFile "test/fixtures/hash-hex-reference.arboricx"
   1572   let digest = unpack (hashObject (Domain "tricu.hash-hex-reference.v1") bytes)
   1573   unless (digest == "b5419c577b5a3e8a4458261bfa6934a43cce40eb86e24326aa9a33d6d0552e81") $
   1574     error "hash-hex reference fixture changed; do not refresh it from the candidate"
   1575   bundle <- either error pure (decodeBundle bytes)
   1576   either error pure (verifyBundle bundle)
   1577   let terms = reconstructBundleTerms (bundleNodes bundle)
   1578   pure $ Map.fromList
   1579     [(unpack (exportName entry), terms V.! fromIntegral (exportRoot entry)) |
   1580       entry <- manifestExports (bundleManifest bundle)]
   1581 {-# NOINLINE hashHexReference #-}
   1582 
   1583 digestHexTests :: TestTree
   1584 digestHexTests = testGroup "guarded digest hex"
   1585   [ testCase "all canonical byte values match integer oracle and old kernel" $
   1586       forM_ [0..255] $ \value -> do
   1587         let input = ofList [ofNumber value]
   1588             expected = if (value >= 48 && value <= 57) ||
   1589                           (value >= 97 && value <= 102) then Stem Leaf else Leaf
   1590         apply old input @?= expected
   1591         apply current input @?= expected
   1592   , testCase "wider, noncanonical and malformed trees retain old behavior" $
   1593       forM_ unusual $ \input -> apply current input @?= apply old input
   1594   , testCase "complete digests and late invalid bytes retain old behavior" $
   1595       forM_ strings $ \input -> apply current (ofString input) @?= apply old (ofString input)
   1596   , testCase "public digest length and alphabet checks remain exact" $ do
   1597       let valid = snd compilerFixture Map.! "source_object.sourceObjectHashValid"
   1598       forM_ [0,1,63,64,65] $ \size ->
   1599         apply valid (ofString (replicate size 'a')) @?= if size == 64 then Stem Leaf else Leaf
   1600       forM_ ['A','G','/', '\NUL', '\255'] $ \byte ->
   1601         apply valid (ofString (replicate 63 'a' ++ [byte])) @?= Leaf
   1602   ]
   1603   where
   1604     fix = hashHexReference Map.! "fix"
   1605     old = apply fix (hashHexReference Map.! "worker")
   1606     current = snd compilerFixture Map.! "source_object.sourceObjectHashHex"
   1607     pad Leaf count = foldr (\_ rest -> Fork Leaf rest) Leaf [1..count]
   1608     pad (Fork bit rest) count = Fork bit (pad rest count)
   1609     pad other _ = other
   1610     unusual =
   1611       map (ofList . (:[]) . ofNumber) [256,257,511,512,65535] ++
   1612       [ofList [pad (ofNumber value) count] |
   1613         value <- [0,1,47,48,49,57,58,96,97,102,103,255], count <- [1,2]] ++
   1614       map (ofList . (:[]))
   1615         [Stem Leaf, Stem (Stem Leaf), Fork (Fork Leaf Leaf) Leaf,
   1616          Fork (Stem (Stem Leaf)) Leaf, Fork Leaf (Stem Leaf)] ++
   1617       [Leaf, Stem Leaf, Stem (Stem Leaf),
   1618        Fork (ofNumber 97) (Stem Leaf), Fork (ofNumber 103) (Stem Leaf)]
   1619     strings = ["0123456789abcdef", replicate 64 'a', replicate 64 '0',
   1620       replicate 64 'f', concat (replicate 4 "0123456789abcdef"),
   1621       'A' : replicate 63 'a', replicate 63 'a' ++ "G",
   1622       replicate 31 'a' ++ "\NUL" ++ replicate 32 'a',
   1623       replicate 31 'a' ++ "\255" ++ replicate 32 'a']
   1624 
   1625 compilerFixtureTests :: TestTree
   1626 compilerFixtureTests = testGroup "self-built reference compiler fixture"
   1627   [ testCase "matches all recorded reference source inputs" $ do
   1628       length (fst compilerFixture) @?= 28
   1629       assertBool "fixture-only patterns still in compiler closure"
   1630         (all (\(_, alias, _) -> alias /= "patterns") (fst compilerFixture))
   1631       assertBool "patterns missing from support cohort"
   1632         (any (\(_, alias, _) -> alias == "patterns") (fst supportFixture))
   1633       assertBool "missing name index exports"
   1634         (all (`Map.member` snd compilerFixture)
   1635           ["source_index.sourceIndexBuild", "source_index.sourceIndexGet", "source_index.sourceIndexPut"])
   1636       assertBool "missing unit compiler export"
   1637         (Map.member "source_unit.compileSource" (snd compilerFixture))
   1638   , testCase "direct fixture interfaces exclude imports and reject unknown modules" $ do
   1639       let http = fixtureExports "http"
   1640       assertBool "missing owned HTTP export" (Map.member "parseHeaders" http)
   1641       forM_ ["append", "kernel", "source_unit.compileSource"] $ \name ->
   1642         assertBool ("leaked imported/injected name: " ++ name) (Map.notMember name http)
   1643       rejected <- try (evaluate (Map.size (fixtureExports "missing.fixture"))) :: IO (Either SomeException Int)
   1644       case rejected of
   1645         Left _ -> pure ()
   1646         Right _ -> assertFailure "unknown fixture module silently accepted"
   1647   , testCase "portable wrapper compilation keeps unused definitions lazy" $ do
   1648       env <- compileFixtureDefinitions Map.empty
   1649         "unused = (x : x x) (x : x x)\nmain = t"
   1650       Map.keys env @?= ["main", "unused"]
   1651       Map.lookup "main" env @?= Just Leaf
   1652   , testCase "portable wrapper compilation rejects invalid source without fallback" $ do
   1653       forM_ ["main = missing", "main = t\nmain = t", "t", "main = #"] $ \source -> do
   1654         rejected <- try (compileFixtureDefinitions Map.empty source) :: IO (Either SomeException Env)
   1655         case rejected of
   1656           Left err -> assertBool "expected portable diagnostic"
   1657             ("portable test compilation failed" `isInfixOf` show err)
   1658           Right _ -> assertFailure ("accepted invalid wrapper: " ++ source)
   1659   , testCase "portable wrapper compilation uses only explicit externals" $ do
   1660       env <- compileFixtureDefinitions (Map.singleton "outside" (Stem Leaf)) "main = outside t"
   1661       Map.keys env @?= ["main"]
   1662       Map.lookup "main" env @?= Just (Fork Leaf Leaf)
   1663       rejected <- try (compileFixtureDefinitions (Map.singleton "outside" Leaf) "outside = t")
   1664         :: IO (Either SomeException Env)
   1665       case rejected of
   1666         Left _ -> pure ()
   1667         Right _ -> assertFailure "wrapper shadows an imported name"
   1668   , testCase "every reference compiler module authors native headers" $ do
   1669       forM_ (fst compilerFixture) $ \(_, _, path) -> do
   1670         source <- readTestSource path
   1671         assertBool ("missing native export: " ++ path)
   1672           (any ("!export " `isPrefixOf`) (lines source))
   1673         assertBool ("legacy import: " ++ path)
   1674           (not (any ("!import \"" `isPrefixOf`) (lines source)))
   1675   , testCase "reference source rejects corrupt bytes and unsafe digests" $
   1676       withSystemTempDirectory "tricu-reference-source" $ \dir -> do
   1677         let bytes = BS.pack [120,32,61,32,116,10]
   1678             pin = unpack (hashObject (Domain (pack "arboricx.source.v1")) bytes)
   1679             path = dir </> (pin ++ ".tri")
   1680             rejected action = do
   1681               result <- try action :: IO (Either SomeException BS.ByteString)
   1682               case result of
   1683                 Left _ -> pure ()
   1684                 Right _ -> assertFailure "invalid reference source accepted"
   1685         rejected (readPinnedSource dir pin)
   1686         BS.writeFile path bytes
   1687         readPinnedSource dir pin >>= (@?= bytes)
   1688         BS.writeFile path (bytes <> BS.singleton 32)
   1689         rejected (readPinnedSource dir pin)
   1690         rejected (readPinnedSource dir "../source")
   1691   , testCase "native prelude retains exactly its dependency re-exports" $ do
   1692       let exports = snd compilerFixture
   1693           owned alias = Map.fromList
   1694             [(drop (length alias + 1) name, tree) | (name, tree) <- Map.toList exports,
   1695               (alias ++ ".") `isPrefixOf` name]
   1696       owned "prelude" @?= Map.unions (map owned ["base", "bytes", "conversions"])
   1697   , testCase "native base effect interpreter and guard use the rebuilt kernels" $ do
   1698       env <- pure (fixtureExports "base")
   1699       let pureValue value = Fork (ofNumber 0) value
   1700           success value rest = Fork (Stem Leaf) (Fork value rest)
   1701           failure value = Fork Leaf (Fork value Leaf)
   1702           value = ofNumber 42
   1703           exception = Fork (ofNumber 99)
   1704             (Fork (ofString "wanted") (Fork value Leaf))
   1705           handler = apply (env Map.! "const")
   1706             (apply (env Map.! "const") (pureValue (ofNumber 9)))
   1707           handled = foldl apply (env Map.! "handleM") [ofString "wanted", handler, exception]
   1708           predicate = apply (env Map.! "const") (Fork Leaf Leaf)
   1709       apply (env Map.! "runM") (pureValue value) @?= success value Leaf
   1710       apply (env Map.! "runM") exception @?= failure value
   1711       apply (env Map.! "runM") (Stem Leaf) @?= Stem Leaf
   1712       handled @?= pureValue (ofNumber 9)
   1713       foldl apply (env Map.! "guardC") [ofString "bad", predicate, value, ofNumber 7]
   1714         @?= success value (ofNumber 7)
   1715   , testCase "mixed support reuse requires all provider trees, including unused ones" $ do
   1716       let before = Map.fromList [("P.used",1),("P.unused",2)]
   1717           source = "!import P as X\n!export f\nf x = X.used x\n"
   1718       verifyReuseProviders before before source @?= Right ()
   1719       forM_ ["P.used", "P.unused"] $ \name ->
   1720         verifyReuseProviders before (Map.insert name 3 before) source @?=
   1721           Left "provider kernels changed: P"
   1722       forM_ [Map.insert "P.extra" 4 before, Map.delete "P.unused" before, Map.empty] $ \after ->
   1723         verifyReuseProviders before after source @?= Left "full export set changed: P"
   1724       verifyReuseProviders Map.empty before source @?= Left "missing original provider: P"
   1725   , testCase "mixed support reuse no longer exempts shadowed imports" $ do
   1726       let before = Map.fromList [("P.used",1),("P.unused",2)]
   1727           shadowed = "!import P as X\n!export f\nf used = used\n"
   1728       verifyReuseProviders before before shadowed @?= Right ()
   1729       verifyReuseProviders before (Map.insert "P.used" 4 before) shadowed @?=
   1730         Left "provider kernels changed: P"
   1731   , testCase "mixed support reuse checks standalone reexports and all import forms" $ do
   1732       let before = Map.fromList [("P.used",1),("P.unused",2),("PQ.other",3)]
   1733       forM_ ["!import P", "!import P *", "!import P as X", "!export * from P",
   1734              "  !import P *", "!import P *\n!export * from P"] $ \header -> do
   1735         let source = header ++ "\n!export f\nf x = x\n"
   1736         verifyReuseProviders before before source @?= Right ()
   1737         verifyReuseProviders before (Map.insert "PQ.other" 4 before) source @?= Right ()
   1738         verifyReuseProviders before (Map.insert "P.used" 4 before) source @?=
   1739           Left "provider kernels changed: P"
   1740       verifyReuseProviders before before "!export * from Missing\n" @?=
   1741         Left "missing original provider: Missing"
   1742   , testCase "mixed support reuse rejects changed namespace collision outcomes" $ do
   1743       let before = Map.fromList [("P.used",1),("Q.used",1)]
   1744           source = "!import P *\n!import Q *\n!export f\nf x = x\n"
   1745       verifyReuseProviders before before source @?= Right ()
   1746       verifyReuseProviders before (Map.insert "Q.used" 2 before) source @?=
   1747         Left "provider kernels changed: Q"
   1748   , testCase "mixed support reuse rejects unsupported dependency directives" $ do
   1749       let before = Map.singleton "P.used" 1
   1750       forM_ ["!import", "!import P as", "!import P * extra", "!import P as *",
   1751              "!import \"P\" *", "!export * from", "!export * from P extra",
   1752              "!source P", "!unknown P", "!import " ++ replicate 64 'a' ++ " *"] $ \header ->
   1753         assertBool ("accepted unsupported reuse header: " ++ header)
   1754           (case verifyReuseProviders before before (header ++ "\n") of Left _ -> True; _ -> False)
   1755   , testCase "mixed support reuse compares canonical trees, not bundle-local node indices" $ do
   1756       let original = buildBundle [("P.used", Stem Leaf), ("P.unused", Fork Leaf Leaf)]
   1757           reordered = buildBundle [("Q.extra", ofNumber 42), ("P.unused", Fork Leaf Leaf),
   1758                                    ("P.used", Stem Leaf)]
   1759           changed = buildBundle [("P.used", Stem Leaf), ("P.unused", Fork Leaf (Stem Leaf))]
   1760       forM_ [original, reordered, changed] $ \bundle -> verifyBundle bundle @?= Right ()
   1761       case canonicalEnvs [original, reordered, changed] of
   1762         [before, same, different] -> do
   1763           verifyReuseProviders before same "!import P *\n" @?= Right ()
   1764           verifyReuseProviders before different "!import P *\n" @?=
   1765             Left "provider kernels changed: P"
   1766         _ -> assertFailure "incorrect canonical environment count"
   1767   , testCase "mixed support reuse permits provider-free unchanged programs" $
   1768       verifyReuseProviders Map.empty Map.empty "-- no dependencies\n!export main\nmain = t\n" @?= Right ()
   1769   , testCase "support publication allows new rebuilt sources, not relabeled retained sources" $ do
   1770       let old = [["module","http","http","lib/http.tri","old-source"]]
   1771           new = [["module","http","http","lib/http.tri","new-source"]]
   1772       verifySupportSources old new (Set.singleton "http") @?= Right ()
   1773       verifySupportSources old old Set.empty @?= Right ()
   1774       verifySupportSources old new Set.empty @?=
   1775         Left "retained source fingerprint changed: http"
   1776       verifySupportSources old [["module","other","http","lib/http.tri","new-source"]]
   1777         (Set.singleton "http") @?= Left "support cohort identity changed"
   1778   , testCase "compiler-to-support relocation requires exact identity and a rebuild" $ do
   1779       let old = [["module","http","http","lib/http.tri","old-source"]]
   1780           added = ["module","patterns","patterns","lib/patterns.tri","new-source"]
   1781           declared = ["relocated","compiler-library","patterns","patterns","lib/patterns.tri"]
   1782           current = old ++ [added, declared]
   1783       verifyRelocatedSupportSources old current (Set.singleton "patterns") @?= Right ()
   1784       forM_ [old ++ [added], old ++ [added, declared, declared],
   1785              old ++ [added, declared, ["module","extra","extra","lib/extra.tri","pin"]],
   1786              old ++ [["module","patterns","patterns","lib/other.tri","pin"], declared]] $ \bad ->
   1787         assertBool "unauthorized relocation accepted"
   1788           (case verifyRelocatedSupportSources old bad (Set.singleton "patterns") of Left _ -> True; _ -> False)
   1789       verifyRelocatedSupportSources old current Set.empty @?= Left "invalid patterns cohort relocation"
   1790       verifySupportSources old current (Set.singleton "patterns") @?= Left "support cohort identity changed"
   1791   , testCase "rebuilt export sets follow the new manifest, not the old interface" $ do
   1792       let manifest kind names = unlines ("arboricx.module-manifest.v1" :
   1793             ["export " ++ name ++ " " ++ kind ++ " hash arboricx.abi.tree.v1" | name <- names])
   1794           names = Set.fromList ["parseHeaders", "httpAsciiBit"]
   1795       forM_ ["arboricx.tree-term.v1", "arboricx.indexed-tree.v1"] $ \kind -> do
   1796         verifySupportExports names (manifest kind (Set.toList names)) @?= Right ()
   1797         forM_ [["parseHeaders"], ["parseHeaders", "httpAsciiBit", "byte7BitsOr"],
   1798                ["parseHeaders", "httpAsciiBit", "httpAsciiBit"]] $ \bad ->
   1799           verifySupportExports names (manifest kind bad) @?= Left "rebuilt manifest export set mismatch"
   1800       verifySupportExports names (manifest "unknown-kind" (Set.toList names))
   1801         @?= Left "rebuilt manifest export set mismatch"
   1802       verifySupportExports names (unlines ["arboricx.module-manifest.v1",
   1803         "export parseHeaders arboricx.tree-term.v1 hash arboricx.abi.tree.v1",
   1804         "export httpAsciiBit arboricx.indexed-tree.v1 hash arboricx.abi.tree.v1"])
   1805         @?= Left "rebuilt manifest export set mismatch"
   1806   , testCase "rebuilt consumers reject stale support provider manifests" $ do
   1807       let providers = Map.singleton "http" "new-manifest"
   1808           rows pin = [["local","http",pin,"facade-source"],
   1809                       ["pin","http",pin,"parseHeaders","tree"]]
   1810       verifySupportDependencies providers (rows "new-manifest") @?= Right ()
   1811       verifySupportDependencies providers (rows "old-manifest") @?=
   1812         Left "stale support dependency: http"
   1813       verifySupportDependencies providers
   1814         [["local","http","new-manifest","source"], ["pin","http","old-manifest","f","tree"]]
   1815         @?= Left "stale support dependency: http"
   1816       verifySupportDependencies providers
   1817         (replicate 2 ["local","http","new-manifest","source"]) @?= Left "duplicate local dependency"
   1818   , testCase "native staging preserves namespaces and explicit exports" $ do
   1819       env <- pure (fixtureExports "source.map")
   1820       let row = pairT (ofString "example") (pairT (ofString "example")
   1821                   (pairT (ofString "example.tri")
   1822                     (pairT (ofList [ofString "native_math"]) (ofString "native"))))
   1823           source = ofString "!import native_math as N\n!export main\nmain = N.add 2 3\n"
   1824           prepare text = apply (apply (apply (env Map.! "sourceMapPrepareBody") row) text) Leaf
   1825       prepare source @?= pairT (Stem Leaf) (pairT source Leaf)
   1826       apply (apply (env Map.! "sourceMapOverlay") row) source @?= source
   1827       forM_ ["", "!source other\n", "!source extra\n"] $ \bad ->
   1828         apply (fixtureExports "base" Map.! "fst") (prepare (ofString bad)) @?= Leaf
   1829   , testCase "shared native header derives ordered deduplicated edges and masks positions" $ do
   1830       env <- pure (fixtureExports "source.module")
   1831       let header = "-- lead\r\n!import math\n!export * from base\n!import math as M\n!source build_only\n!export main\n"
   1832           body = "main = missing\n"
   1833           masked = map (\c -> if c == '\n' || c == '\r' then c else ' ') header ++ body
   1834           wordsT = ofList . map ofString
   1835           staged = pairT (wordsT ["math", "base", "build_only"])
   1836             (pairT (ofList [wordsT ["math"], wordsT ["math", "as", "M"]])
   1837               (pairT (ofList [wordsT ["*", "from", "base"], wordsT ["main"]]) (ofString masked)))
   1838       apply (env Map.! "sourceModuleHeaderStage") (ofString (header ++ body)) @?=
   1839         pairT (Stem Leaf) (pairT staged Leaf)
   1840   , testCase "native header rejects redundant edges and bare hashes at original locations" $ do
   1841       env <- pure (fixtureExports "source.module")
   1842       let bad sources = forM_ sources $ \source ->
   1843             apply (fixtureExports "base" Map.! "fst") (apply (env Map.! "sourceModuleHeaderStage") (ofString source)) @?= Leaf
   1844       bad ["!source dep\n!import dep\n", "!import dep\n!source dep\n",
   1845            "!source dep\n!export * from dep\n", "!source dep\n!source dep\n",
   1846            "!import " ++ replicate 64 'a' ++ "\n", "!use dep *\n", "!import \"dep\" !Local\n"]
   1847       let outcome = apply (env Map.! "sourceModuleHeaderStage")
   1848             (ofString "-- lead\n\n!use dep *\n")
   1849           problem = apply (fixtureExports "base" Map.! "fst") (apply (fixtureExports "base" Map.! "snd") outcome)
   1850       apply (fixtureExports "parser" Map.! "parserDiagnosticLocation") problem @?= gatePosition 3 1 9
   1851   , testCase "native math exports are public only and general equality remains" $ do
   1852       env <- pure (fixtureExports "math.native")
   1853       assertBool "missing math decimal" (Map.member "decimal" env)
   1854       let owned = [name | name <- Map.keys (snd compilerFixture), "native_math." `isPrefixOf` name]
   1855       length owned @?= 27
   1856       forM_ ["divMod", "quotient", "remainder", "power"] $ \name ->
   1857         assertBool ("missing native arithmetic export: " ++ name) (Map.member name env)
   1858       assertBool "private math helper exported" (not ("native_math.compareStep" `elem` owned))
   1859       prelude <- pure (fixtureExports "prelude")
   1860       assertBool "general equality removed" (Map.member "equal?" prelude)
   1861   ]
   1862 
   1863 gateParserEnv :: Env
   1864 gateParserEnv = fixtureExports "source.parser"
   1865 {-# NOINLINE gateParserEnv #-}
   1866 
   1867 gateAst :: Integer -> T -> T -> T -> T
   1868 gateAst kind payload start end =
   1869   pairT (ofNumber kind) (pairT payload (pairT start end))
   1870 
   1871 gateParserTests :: TestTree
   1872 gateParserTests = testGroup "Feasibility-gate source parser"
   1873   [ testCase "self-built parser export executes through its wrapper" $ do
   1874       env <- evaluateCompilerFile "./test/sourceGateParserImport.tri"
   1875       Map.lookup "main" env @?= Just
   1876         (portableReply True
   1877           (gateAst 1 Leaf (gatePosition 1 1 0) (gatePosition 1 2 1))
   1878           Leaf True False)
   1879   , testCase "leaf retains its source span" $
   1880       check "t" (portableReply True
   1881         (gateAst 1 Leaf (gatePosition 1 1 0) (gatePosition 1 2 1))
   1882         Leaf True False)
   1883   , testCase "decimal integer is a located value" $
   1884       check "007" (portableReply True
   1885         (gateAst 6 (ofNumber 7) (gatePosition 1 1 0) (gatePosition 1 4 3))
   1886         Leaf True False)
   1887   , testCase "string is a located decoded value" $
   1888       check "\"hi\"" (portableReply True
   1889         (gateAst 7 (ofString "hi") (gatePosition 1 1 0) (gatePosition 1 5 4))
   1890         Leaf True False)
   1891   , testCase "empty list retains its brackets and canonical empty payload" $
   1892       check "[]" (portableReply True
   1893         (gateAst 8 Leaf (gatePosition 1 1 0) (gatePosition 1 3 2))
   1894         Leaf True False)
   1895   , testCase "list elements are separate atoms, not an application" $
   1896       let leaf1 = gateAst 1 Leaf (gatePosition 1 2 1) (gatePosition 1 3 2)
   1897           leaf2 = gateAst 1 Leaf (gatePosition 1 4 3) (gatePosition 1 5 4)
   1898       in check "[t t]" (portableReply True
   1899         (gateAst 8 (ofList [leaf1, leaf2])
   1900           (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf True False)
   1901   , testCase "nested lists and grouped applications retain distinct spans" $
   1902       let innerLeaf = gateAst 1 Leaf
   1903             (gatePosition 1 3 2) (gatePosition 1 4 3)
   1904           innerList = gateAst 8 (ofList [innerLeaf])
   1905             (gatePosition 1 2 1) (gatePosition 1 5 4)
   1906           left = gateAst 1 Leaf (gatePosition 1 7 6) (gatePosition 1 8 7)
   1907           right = gateAst 1 Leaf (gatePosition 1 9 8) (gatePosition 1 10 9)
   1908           app = gateAst 3 (pairT left right)
   1909             (gatePosition 1 7 6) (gatePosition 1 10 9)
   1910           group = gateAst 5 app (gatePosition 1 6 5) (gatePosition 1 11 10)
   1911       in check "[[t] (t t)]" (portableReply True
   1912         (gateAst 8 (ofList [innerList, group])
   1913           (gatePosition 1 1 0) (gatePosition 1 12 11)) Leaf True False)
   1914   , testCase "unclosed list reports the expected closing bracket" $
   1915       check "[t" (portableReply False
   1916         (portableDiagnostic (ofNumber 2) (ofNumber 10) Leaf Leaf Leaf)
   1917         Leaf True False)
   1918   , testCase "unexpected separator retains its token and location" $
   1919       check "[t : t]" (portableReply False
   1920         (portableDiagnostic (ofNumber 2) (ofNumber 10) (ofNumber 5)
   1921           (gatePosition 1 4 3) Leaf)
   1922         (ofList
   1923           [ gateToken 5 Leaf (gatePosition 1 4 3) (gatePosition 1 5 4)
   1924           , gateToken 1 (ofString "t")
   1925               (gatePosition 1 6 5) (gatePosition 1 7 6)
   1926           , gateToken 10 Leaf (gatePosition 1 7 6) (gatePosition 1 8 7)
   1927           ]) True False)
   1928   , testCase "application is left associative" $
   1929       let name = gateAst 2 (ofString "f") (gatePosition 1 1 0) (gatePosition 1 2 1)
   1930           arg1 = gateAst 1 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3)
   1931           arg2 = gateAst 1 Leaf (gatePosition 1 5 4) (gatePosition 1 6 5)
   1932           first = gateAst 3 (pairT name arg1) (gatePosition 1 1 0) (gatePosition 1 4 3)
   1933       in check "f t t" (portableReply True
   1934         (gateAst 3 (pairT first arg2) (gatePosition 1 1 0) (gatePosition 1 6 5))
   1935         Leaf True False)
   1936   , testCase "let retains binding, value, body, and complete span" $
   1937       let name = gateToken 2 (ofString "x")
   1938             (gatePosition 1 5 4) (gatePosition 1 6 5)
   1939           value = gateAst 1 Leaf (gatePosition 1 9 8) (gatePosition 1 10 9)
   1940           body = gateAst 2 (ofString "x")
   1941             (gatePosition 1 14 13) (gatePosition 1 15 14)
   1942       in check "let x = t in x" (portableReply True
   1943         (gateAst 9 (pairT name (pairT value body))
   1944           (gatePosition 1 1 0) (gatePosition 1 15 14)) Leaf True False)
   1945   , testCase "where retains the span of the whole expression" $
   1946       let name = gateToken 2 (ofString "x")
   1947             (gatePosition 1 9 8) (gatePosition 1 10 9)
   1948           value = gateAst 1 Leaf (gatePosition 1 13 12) (gatePosition 1 14 13)
   1949           body = gateAst 2 (ofString "x")
   1950             (gatePosition 1 1 0) (gatePosition 1 2 1)
   1951       in check "x where x = t" (portableReply True
   1952         (gateAst 9 (pairT name (pairT value body))
   1953           (gatePosition 1 1 0) (gatePosition 1 14 13)) Leaf True False)
   1954   , testCase "a top-level newline does not silently join application operands" $
   1955       check "t\n t" (portableReply False
   1956         (portableDiagnostic (ofNumber 2) Leaf (ofNumber 6)
   1957           (gatePosition 1 2 1) Leaf)
   1958         (ofList
   1959           [ gateToken 6 Leaf
   1960               (gatePosition 1 2 1) (gatePosition 2 1 2)
   1961           , gateToken 1 (ofString "t")
   1962               (gatePosition 2 2 3) (gatePosition 2 3 4)
   1963           ]) True False)
   1964   , testCase "parentheses permit multiline application with original spans" $
   1965       let left = gateAst 1 Leaf
   1966             (gatePosition 1 2 1) (gatePosition 1 3 2)
   1967           right = gateAst 1 Leaf
   1968             (gatePosition 2 2 4) (gatePosition 2 3 5)
   1969           application = gateAst 3 (pairT left right)
   1970             (gatePosition 1 2 1) (gatePosition 2 3 5)
   1971       in check "(t\n t)" (portableReply True
   1972         (gateAst 5 application
   1973           (gatePosition 1 1 0) (gatePosition 2 4 6)) Leaf True False)
   1974   , testCase "pipe retains source order and direction before elaboration" $
   1975       let value = gateAst 6 (ofNumber 2)
   1976             (gatePosition 1 1 0) (gatePosition 1 2 1)
   1977           name = gateAst 2 (ofString "f")
   1978             (gatePosition 1 6 5) (gatePosition 1 7 6)
   1979       in check "2 |> f" (portableReply True
   1980         (gateAst 10 (pairT (ofNumber 16) (pairT value name))
   1981           (gatePosition 1 1 0) (gatePosition 1 7 6)) Leaf True False)
   1982   , testCase "do retains operator, located binders, and statements" $
   1983       let op = gateAst 2 (ofString "b")
   1984             (gatePosition 1 4 3) (gatePosition 1 5 4)
   1985           name = gateToken 2 (ofString "x")
   1986             (gatePosition 2 3 7) (gatePosition 2 4 8)
   1987           action = gateAst 6 (ofNumber 2)
   1988             (gatePosition 2 8 12) (gatePosition 2 9 13)
   1989           body = gateAst 2 (ofString "x")
   1990             (gatePosition 3 3 16) (gatePosition 3 4 17)
   1991       in check "do b\n  x <- 2\n  x" (portableReply True
   1992         (gateAst 11 (pairT op (ofList
   1993           [pairT (Stem name) action, pairT Leaf body]))
   1994           (gatePosition 1 1 0) (gatePosition 3 4 17)) Leaf True False)
   1995   , testCase "single-argument lambda preserves binder and body" $
   1996       let body = gateAst 2 (ofString "x") (gatePosition 1 5 4) (gatePosition 1 6 5)
   1997       in check "x : x" (portableReply True
   1998         (gateAst 4 (pairT (ofString "x") body)
   1999           (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf True False)
   2000   , testCase "multiple same-line lambda binders elaborate in source order" $
   2001       let body = gateAst 2 (ofString "a")
   2002             (gatePosition 1 7 6) (gatePosition 1 8 7)
   2003           inner = gateAst 4 (pairT (ofString "b") body)
   2004             (gatePosition 1 3 2) (gatePosition 1 8 7)
   2005       in check "a b : a" (portableReply True
   2006         (gateAst 4 (pairT (ofString "a") inner)
   2007           (gatePosition 1 1 0) (gatePosition 1 8 7)) Leaf True False)
   2008   , testCase "lambda body may start on a later line" $
   2009       let body = gateAst 2 (ofString "x")
   2010             (gatePosition 2 3 6) (gatePosition 2 4 7)
   2011       in check "x :\n  x" (portableReply True
   2012         (gateAst 4 (pairT (ofString "x") body)
   2013           (gatePosition 1 1 0) (gatePosition 2 4 7)) Leaf True False)
   2014   , testCase "nested lambdas associate to the right" $
   2015       let body = gateAst 2 (ofString "x") (gatePosition 1 9 8) (gatePosition 1 10 9)
   2016           inner = gateAst 4 (pairT (ofString "y") body)
   2017             (gatePosition 1 5 4) (gatePosition 1 10 9)
   2018       in check "x : y : x" (portableReply True
   2019         (gateAst 4 (pairT (ofString "x") inner)
   2020           (gatePosition 1 1 0) (gatePosition 1 10 9)) Leaf True False)
   2021   , testCase "grouping preserves its outer span" $
   2022       let leaf = gateAst 1 Leaf (gatePosition 1 2 1) (gatePosition 1 3 2)
   2023           group = gateAst 5 leaf (gatePosition 1 1 0) (gatePosition 1 4 3)
   2024           arg = gateAst 1 Leaf (gatePosition 1 5 4) (gatePosition 1 6 5)
   2025       in check "(t) t" (portableReply True
   2026         (gateAst 3 (pairT group arg)
   2027           (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf True False)
   2028   , testCase "empty input reports expected atom" $
   2029       check "" (portableReply False
   2030         (portableDiagnostic (ofNumber 2) (ofNumber 7) Leaf Leaf Leaf)
   2031         Leaf False False)
   2032   , testCase "opened group fails after consumption" $
   2033       check "t (" (portableReply False
   2034         (portableDiagnostic (ofNumber 2) (ofNumber 7) Leaf Leaf Leaf)
   2035         Leaf True False)
   2036   , testCase "missing lambda body is a consumed parse failure" $
   2037       check "x : " (portableReply False
   2038         (portableDiagnostic (ofNumber 2) (ofNumber 7) Leaf Leaf Leaf)
   2039         Leaf True False)
   2040   , testCase "unmatched close paren retains its span" $
   2041       check "t)" (portableReply False
   2042         (portableDiagnostic (ofNumber 2) Leaf (ofNumber 4)
   2043           (gatePosition 1 2 1) Leaf)
   2044         (ofList [gateToken 4 Leaf (gatePosition 1 2 1) (gatePosition 1 3 2)])
   2045         True False)
   2046   , testCase "lexical error passes through the source parser" $
   2047       check "t #" (portableReply False
   2048         (portableDiagnostic (ofNumber 4) Leaf (ofNumber 35)
   2049           (gatePosition 1 3 2) Leaf)
   2050         (ofString "#") True False)
   2051   , testCase "newline does not turn into application whitespace" $
   2052       check "t\nfoo" (portableReply False
   2053         (portableDiagnostic (ofNumber 2) Leaf (ofNumber 6)
   2054           (gatePosition 1 2 1) Leaf)
   2055         (ofList
   2056           [ gateToken 6 Leaf (gatePosition 1 2 1) (gatePosition 2 1 2)
   2057           , gateToken 2 (ofString "foo") (gatePosition 2 1 2) (gatePosition 2 4 5)
   2058           ]) True False)
   2059   ]
   2060   where
   2061     check input expected =
   2062       apply (gateParserEnv Map.! "parseGateSource") (ofString input) @?= expected
   2063 
   2064 -- --------------------------------------------------------------------------
   2065 -- Program-level feasibility-gate parsing (definitions and final expression)
   2066 -- --------------------------------------------------------------------------
   2067 
   2068 equationHelperEnv :: Env
   2069 equationHelperEnv = fixtureExports "source.equations"
   2070 {-# NOINLINE equationHelperEnv #-}
   2071 
   2072 equationHelperTests :: TestTree
   2073 equationHelperTests = testGroup "Equation helper pattern adoption"
   2074   [ testCase "expected token consumes one cell and preserves suffix" $
   2075       expect 7 input @?= portableReply True token suffix True False
   2076   , testCase "wrong token preserves full input and original location" $
   2077       expect 2 input @?= portableReply False
   2078         (portableDiagnostic (ofNumber 2) (ofNumber 2) (ofNumber 7) start Leaf)
   2079         input False False
   2080   , testCase "empty token input has unchanged end diagnostic" $
   2081       expect 2 Leaf @?= endReply 2 Leaf
   2082   , testCase "non-list token input has unchanged end diagnostic" $
   2083       expect 2 (Stem Leaf) @?= endReply 2 (Stem Leaf)
   2084   , testCase "labelled error preserves token kind span and full remainder" $
   2085       labelled input @?= portableReply False
   2086         (portableDiagnostic (ofNumber 48) label (ofNumber 7) start Leaf)
   2087         input False False
   2088   , testCase "labelled empty error retains protocol flags" $
   2089       labelled Leaf @?= labelledEnd Leaf
   2090   , testCase "labelled non-list error retains original input" $
   2091       labelled (Stem Leaf) @?= labelledEnd (Stem Leaf)
   2092   , testCase "empty pattern prefix returns supplied terminator" $
   2093       list Leaf @?= nil
   2094   , testCase "non-list pattern prefix returns supplied terminator" $
   2095       list (Stem Leaf) @?= nil
   2096   , testCase "pattern prefix has independent nested payload and span golden" $
   2097       list (ofList [left, right]) @?= joined left (joined right nil)
   2098   , testCase "improper pattern suffix retains prior non-list termination" $
   2099       list (Fork left (Stem Leaf)) @?= joined left nil
   2100   , testCase "empty pattern list retains an opaque supplied tail" $
   2101       apply (fixtureExports "base" Map.! "fst")
   2102         (call "sourceEquationList" [Leaf, Fork (ofNumber 23)
   2103           (error "unused suffix demanded")]) @?= ofNumber 23
   2104   ]
   2105   where
   2106     call name args = foldl apply (equationHelperEnv Map.! name) args
   2107     start = gatePosition 2 3 5
   2108     end = gatePosition 2 4 6
   2109     token = gateToken 7 (ofNumber 23) start end
   2110     suffix = ofList [Leaf]
   2111     input = Fork token suffix
   2112     label = ofString "pattern"
   2113     expect kind value = call "sourceEquationExpect" [ofNumber kind, value]
   2114     labelled value = call "sourceEquationError" [label, value]
   2115     endReply kind value = portableReply False
   2116       (portableDiagnostic (ofNumber 2) (ofNumber kind) Leaf Leaf Leaf)
   2117       value False False
   2118     labelledEnd value = portableReply False
   2119       (portableDiagnostic (ofNumber 48) label Leaf Leaf Leaf) value False False
   2120     patternNode tag payload a b = gateAst 13 (pairT (ofNumber tag) payload) a b
   2121     nil = patternNode 1 Leaf (gatePosition 2 7 9) (gatePosition 2 8 10)
   2122     left = patternNode 4 (ofString "x") start end
   2123     right = patternNode 4 (ofString "y") (gatePosition 2 5 7) (gatePosition 2 6 8)
   2124     joined a b = patternNode 3 (pairT a b)
   2125       (case a of Fork _ (Fork _ (Fork x _)) -> x; _ -> error "invalid golden")
   2126       (gatePosition 2 8 10)
   2127     list value = call "sourceEquationList" [value, nil]
   2128 
   2129 gateProgramEnv :: Env
   2130 gateProgramEnv = fixtureExports "source.program"
   2131 {-# NOINLINE gateProgramEnv #-}
   2132 
   2133 gateProgramTests :: TestTree
   2134 gateProgramTests = testGroup "Feasibility-gate program parser"
   2135   [ testCase "portable wrapper normalizes the definition parser" $ do
   2136       env <- evaluateCompilerFile
   2137         "./test/sourceGateProgramImport.tri"
   2138       Map.lookup "main" env @?= Just programResult
   2139   , testCase "definition arguments elaborate to located lambdas" $ do
   2140       check "id x = x\nid t" programResult
   2141   , testCase "an identifier-led expression without assignment stays an expression" $
   2142       let name = gateAst 2 (ofString "f")
   2143             (gatePosition 1 1 0) (gatePosition 1 2 1)
   2144           leaf = gateAst 1 Leaf
   2145             (gatePosition 1 3 2) (gatePosition 1 4 3)
   2146           expression = gateAst 3 (pairT name leaf)
   2147             (gatePosition 1 1 0) (gatePosition 1 4 3)
   2148       in check "f t" (portableReply True
   2149            (pairT Leaf (Stem expression)) Leaf True False)
   2150   , testCase "unfinished definition fails after consuming the assignment" $
   2151       check "id x =" (portableReply False
   2152         (portableDiagnostic (ofNumber 2) (ofNumber 7) Leaf Leaf Leaf)
   2153         Leaf True False)
   2154   , testCase "an empty unit has no definitions or final expression" $
   2155       check "" (portableReply True (pairT Leaf Leaf) Leaf False False)
   2156   ]
   2157   where
   2158     programResult =
   2159       let name = gateToken 2 (ofString "id")
   2160             (gatePosition 1 1 0) (gatePosition 1 3 2)
   2161           bodyName = gateAst 2 (ofString "x")
   2162             (gatePosition 1 8 7) (gatePosition 1 9 8)
   2163           body = gateAst 4 (pairT (ofString "x") bodyName)
   2164             (gatePosition 1 4 3) (gatePosition 1 9 8)
   2165           expressionName = gateAst 2 (ofString "id")
   2166             (gatePosition 2 1 9) (gatePosition 2 3 11)
   2167           leaf = gateAst 1 Leaf
   2168             (gatePosition 2 4 12) (gatePosition 2 5 13)
   2169           expression = gateAst 3 (pairT expressionName leaf)
   2170             (gatePosition 2 1 9) (gatePosition 2 5 13)
   2171       in portableReply True
   2172            (pairT (ofList [pairT name body]) (Stem expression)) Leaf True False
   2173     check input expected =
   2174       apply (gateProgramEnv Map.! "parseGateProgram") (ofString input) @?= expected
   2175 
   2176 -- --------------------------------------------------------------------------
   2177 -- Portable, stable dependency ordering and cycle witnesses
   2178 -- --------------------------------------------------------------------------
   2179 
   2180 sourceGraphEnv :: Env
   2181 sourceGraphEnv = fixtureExports "graph"
   2182 {-# NOINLINE sourceGraphEnv #-}
   2183 
   2184 sourceGraphTests :: TestTree
   2185 sourceGraphTests = testGroup "Source dependency graph"
   2186   [ testCase "checked native graph sorts dependencies before dependents" $ do
   2187         env <- evaluateCompilerFile "./test/sourceGraphImport.tri"
   2188         Map.lookup "main" env @?= Just
   2189           (parserOk (ofList (map ofString ["leaf", "inner", "outer"])) Leaf)
   2190   , testCase "sort preserves source order among simultaneously ready nodes" $
   2191       check [("b", []), ("a", [])]
   2192         (parserOk (ofList [ofString "b", ofString "a"]) Leaf)
   2193   , testCase "cycle witness is in the cycle, not just a dependent" $
   2194       check [("outside", ["inside"]), ("inside", ["last"]), ("last", ["inside"])]
   2195         (parserErr (ofString "inside") Leaf)
   2196   ]
   2197   where
   2198     check entries expected =
   2199       apply (sourceGraphEnv Map.! "graphSort")
   2200         (ofList [Fork (ofString name) (ofList (map ofString dependencies))
   2201                 | (name, dependencies) <- entries]) @?= expected
   2202 
   2203 -- --------------------------------------------------------------------------
   2204 -- Program-level free names and dependency ordering
   2205 -- --------------------------------------------------------------------------
   2206 
   2207 gateOrderingEnv :: Env
   2208 gateOrderingEnv = fixtureExports "source.order"
   2209 {-# NOINLINE gateOrderingEnv #-}
   2210 
   2211 gateOrderingTests :: TestTree
   2212 gateOrderingTests = testGroup "Feasibility-gate definition ordering"
   2213   [ testCase "portable wrapper orders forward references" $ do
   2214       env <- evaluateCompilerFile
   2215         "./test/sourceGateOrderImport.tri"
   2216       Map.lookup "main" env @?= Just (parserOk
   2217         (ofList [ofString "f", ofString "main"]) Leaf)
   2218   , testCase "unused definitions still have their names checked" $
   2219       check Leaf "main = t\nbroken = u" (parserErr
   2220         (portableDiagnostic (ofNumber 20) (ofString "u") Leaf
   2221           (gatePosition 2 10 18) Leaf)
   2222         (gateAst 2 (ofString "u")
   2223           (gatePosition 2 10 18) (gatePosition 2 11 19)))
   2224   , testCase "lexical binder shadows a definition and external name" $
   2225       check (ofList [Fork (ofString "x") Leaf]) "x = t\nf x = x" (parserOk
   2226         (ofList [ofString "x", ofString "f"]) Leaf)
   2227   , testCase "top-level self reference returns a cycle witness" $
   2228       check Leaf "f = f" (parserErr
   2229         (portableDiagnostic (ofNumber 31) (ofString "acyclic")
   2230           (ofString "f") (gatePosition 1 1 0)
   2231           (ofList [ofString "f"])) Leaf)
   2232   , testCase "duplicate definition reports the later name location" $
   2233       let name = gateToken 2 (ofString "f")
   2234             (gatePosition 2 1 6) (gatePosition 2 2 7)
   2235           body = gateAst 1 Leaf
   2236             (gatePosition 2 5 10) (gatePosition 2 6 11)
   2237       in check Leaf "f = t\nf = t" (parserErr
   2238            (portableDiagnostic (ofNumber 30) (ofString "f")
   2239              (ofString "f") (gatePosition 2 1 6) Leaf)
   2240            (pairT name body))
   2241   , testCase "a final expression also checks unresolved names" $
   2242       check Leaf "f = t\nu" (parserErr
   2243         (portableDiagnostic (ofNumber 20) (ofString "u") Leaf
   2244           (gatePosition 2 1 6) Leaf)
   2245         (gateAst 2 (ofString "u")
   2246           (gatePosition 2 1 6) (gatePosition 2 2 7)))
   2247   ]
   2248   where
   2249     check environment source expected =
   2250       foldl apply (gateOrderingEnv Map.! "orderGateSource") [environment, ofString source]
   2251         @?= expected
   2252 
   2253 -- --------------------------------------------------------------------------
   2254 -- End-to-end program compilation through ordered definitions
   2255 -- --------------------------------------------------------------------------
   2256 
   2257 gateUnitEnv :: Env
   2258 gateUnitEnv = unsafePerformIO $ evaluateCompilerFile "./lib/source/unit.tri"
   2259 {-# NOINLINE gateUnitEnv #-}
   2260 
   2261 gateUnitTests :: TestTree
   2262 gateUnitTests = testGroup "Feasibility-gate program compiler"
   2263   [ testCase "real source files compile without rewriting or relexing" $ do
   2264       let corpus =
   2265             [ ("./test/fork.tri", [], Fork Leaf Leaf)
   2266             , ("./test/comments-1.tri", [], Fork (Stem Leaf) Leaf)
   2267             , ("./test/map.tri", ["map", "append", "equal?", "head"], trueT)
   2268             ]
   2269       forM_ corpus $ \(path, names, expected) -> do
   2270         source <- readFile path
   2271         let external = ofList [Fork (ofString name) (fixtureExports "base" Map.! name) | name <- names]
   2272         foldl apply (fixtureExports "source.unit" Map.! "compileGateProgram")
   2273           [external, ofString source] @?= parserOk expected Leaf
   2274   , testCase "portable size counts leaf, stem, and fork arguments" $ do
   2275       source <- readFile "./test/size.tri"
   2276       let definitions = unlines (init (lines source))
   2277           external = ofList [Fork (ofString name) (fixtureExports "base" Map.! name)
   2278                             | name <- ["y", "triage"]]
   2279       forM_ [("t", 1), ("t t", 2), ("t t t", 3)] $ \(argument, count) -> do
   2280         let input = definitions ++ "size (" ++ argument ++ ")\n"
   2281         foldl apply (fixtureExports "source.unit" Map.! "compileGateProgram")
   2282           [external, ofString input] @?= parserOk (ofNumber count) Leaf
   2283   , testCase "real file inputs match the natively packed self-built artifact" $
   2284       withSystemTempDirectory "gate-file-corpus" $ \dir -> do
   2285         let path = dir </> "file-matches.arboricx"
   2286             corpus =
   2287               [ ("./test/fork.tri", parserOk (Fork Leaf Leaf) Leaf)
   2288               , ("./test/comments-1.tri",
   2289                   parserOk (Fork (Stem Leaf) Leaf) Leaf)
   2290               , ("./test/map.tri", parserOk trueT Leaf)
   2291               , -- Optimized unit lowering with constructor-K-compiled y/triage.
   2292               -- Independently counted the emitted size tree on the host: 500
   2293               -- nodes (formerly 680 with the old support representations).
   2294               ("./test/size.tri", parserOk (ofNumber 500) Leaf)
   2295               ]
   2296         packCompilerFile
   2297           "./test/sourceGateFileCompiler.tri" path [pack "matchesFile"]
   2298         bytes <- BS.readFile path
   2299         case decodeBundle bytes of
   2300           Left diagnostic -> assertFailure diagnostic
   2301           Right bundle -> do
   2302             let artifact = reconstructBundleTerms (bundleNodes bundle)
   2303                   V.! fromIntegral (head (bundleRoots bundle))
   2304             forM_ corpus $ \(fixture, expected) -> do
   2305               source <- readFile fixture
   2306               apply (apply artifact (ofString source)) expected @?= trueT
   2307   , testCase "self-built local binding compiler executes through its wrapper" $ do
   2308       env <- evaluateCompilerFile
   2309         "./test/sourceGateLocalImport.tri"
   2310       Map.lookup "main" env @?= Just (parserOk Leaf Leaf)
   2311   , testCase "independent program goldens match the Haskell bundle" $
   2312       withSystemTempDirectory "gate-unit-goldens" $ \dir -> do
   2313         let path = dir </> "program-matches.arboricx"
   2314         packCompilerFile
   2315           "./test/sourceGateUnitCompiler.tri" path [pack "matchesExpected"]
   2316         bytes <- BS.readFile path
   2317         goldenEnv <- evaluateCompilerFile "./test/sourceGateExpected.tri"
   2318         let name = gateToken 2 (ofString "f")
   2319               (gatePosition 2 1 6) (gatePosition 2 2 7)
   2320             body = gateAst 1 Leaf
   2321               (gatePosition 2 5 10) (gatePosition 2 6 11)
   2322             cycleError = parserErr
   2323               (portableDiagnostic (ofNumber 31) (ofString "acyclic")
   2324                 (ofString "a") (gatePosition 1 1 0)
   2325                 (ofList [ofString "a"])) Leaf
   2326             cases =
   2327               [ ("main = id 42\nid x = x", "goldenFortyTwo",
   2328                   parserOk (ofNumber 42) Leaf)
   2329               , ("id x = x\nid 42", "goldenFortyTwo",
   2330                   parserOk (ofNumber 42) Leaf)
   2331               , ("main = 2\n3", "goldenTwo", parserOk (ofNumber 2) Leaf)
   2332               , ("main = [(id 2)]\nid x = x", "goldenProgramList",
   2333                   parserOk (ofList [ofNumber 2]) Leaf)
   2334               , ("helper = t", "goldenMissingRoot", parserErr
   2335                   (portableDiagnostic (ofNumber 32) (ofString "main")
   2336                     Leaf Leaf Leaf) Leaf)
   2337               , ("a = b\nb = a\nmain = t", "goldenProgramCycle", cycleError)
   2338               , ("f = t\nf = t", "goldenProgramDuplicate", parserErr
   2339                   (portableDiagnostic (ofNumber 30) (ofString "f")
   2340                     (ofString "f") (gatePosition 2 1 6) Leaf)
   2341                   (pairT name body))
   2342               , ("main = u", "goldenProgramUnknown", parserErr
   2343                   (portableDiagnostic (ofNumber 20) (ofString "u") Leaf
   2344                     (gatePosition 1 8 7) Leaf)
   2345                   (gateAst 2 (ofString "u")
   2346                     (gatePosition 1 8 7) (gatePosition 1 9 8)))
   2347               , ("unused = (x : x x) (x : x x)\nmain = t", "goldenLeaf",
   2348                   parserOk Leaf Leaf)
   2349               , ("main = let x = t in x", "goldenLeaf", parserOk Leaf Leaf)
   2350               , ("main = x where x = t", "goldenLeaf", parserOk Leaf Leaf)
   2351               , ("main = let id x = x in id 2", "goldenTwo",
   2352                   parserOk (ofNumber 2) Leaf)
   2353               , ("main = value where value = helper t\nhelper x = x",
   2354                   "goldenLeaf", parserOk Leaf Leaf)
   2355               , ("main = let x = x in x", "goldenLocalMissing", parserErr
   2356                   (portableDiagnostic (ofNumber 20) (ofString "x") Leaf
   2357                     (gatePosition 1 16 15) Leaf)
   2358                   (gateAst 2 (ofString "x")
   2359                     (gatePosition 1 16 15) (gatePosition 1 17 16)))
   2360               , ("main = let x = t x", "goldenLetMissingIn", parserErr
   2361                   (portableDiagnostic (ofNumber 2) (ofNumber 13)
   2362                     Leaf Leaf Leaf) Leaf)
   2363               , ("main = (t\n t)", "goldenMultilineGroup",
   2364                   parserOk (Stem Leaf) Leaf)
   2365               , ("main = [(t\n t)]", "goldenMultilineList",
   2366                   parserOk (ofList [Stem Leaf]) Leaf)
   2367               , ("main = (x\n where x = t)", "goldenLeaf",
   2368                   parserOk Leaf Leaf)
   2369               , ("main =\n  t", "goldenLeaf", parserOk Leaf Leaf)
   2370               , ("main =\nt", "goldenLeaf", parserOk Leaf Leaf)
   2371               , ("main =\n  (x : x)\n    2", "goldenTwo",
   2372                   parserOk (ofNumber 2) Leaf)
   2373               , ("main =\n  id\n    t\nid x = x", "goldenLeaf",
   2374                   parserOk Leaf Leaf)
   2375               , ("main = value\nwhere value = t", "goldenLeaf",
   2376                   parserOk Leaf Leaf)
   2377               , ("main =\n  value\n  where value = t", "goldenLeaf",
   2378                   parserOk Leaf Leaf)
   2379               , ("main =\n", "goldenMissingDefinitionBody", parserErr
   2380                   (portableDiagnostic (ofNumber 40)
   2381                     (ofString "definition body") Leaf Leaf Leaf) Leaf)
   2382               , ("main = let x =\n  2\nin x", "goldenTwo",
   2383                   parserOk (ofNumber 2) Leaf)
   2384               , ("main = let x = t in\n  x", "goldenLeaf",
   2385                   parserOk Leaf Leaf)
   2386               , ("main = let\n  id x =\n    x\nin id 2", "goldenTwo",
   2387                   parserOk (ofNumber 2) Leaf)
   2388               , ("main =\n  let x =\n    t\n  in x", "goldenLeaf",
   2389                   parserOk Leaf Leaf)
   2390               , ("main = t where x =\n  2", "goldenLeaf",
   2391                   parserOk Leaf Leaf)
   2392               , ("main = id 2 where\n  id x =\n    x", "goldenTwo",
   2393                   parserOk (ofNumber 2) Leaf)
   2394               , ("main = let x =\n  (y : y)\n    2\nin x", "goldenTwo",
   2395                   parserOk (ofNumber 2) Leaf)
   2396               , ("main = let id x = x in\n  id\n    2", "goldenTwo",
   2397                   parserOk (ofNumber 2) Leaf)
   2398               , ("main = let x =\n", "goldenMissingLocalValue", parserErr
   2399                   (portableDiagnostic (ofNumber 40)
   2400                     (ofString "local binding value") Leaf Leaf Leaf) Leaf)
   2401               , ("main = let x = t in\n", "goldenMissingLocalBody", parserErr
   2402                   (portableDiagnostic (ofNumber 40)
   2403                     (ofString "local binding body") Leaf Leaf Leaf) Leaf)
   2404               , ("main = 2 |> (x : x)", "goldenTwo",
   2405                   parserOk (ofNumber 2) Leaf)
   2406               , ("main = (x : x) <| 2", "goldenTwo",
   2407                   parserOk (ofNumber 2) Leaf)
   2408               , ("main = 2 |> (x : x) |> (x : x)", "goldenTwo",
   2409                   parserOk (ofNumber 2) Leaf)
   2410               , ("main = t <| t t", "goldenPipeApplication",
   2411                   parserOk (Stem (Stem Leaf)) Leaf)
   2412               , ("main = 2 |> id\nid x = x", "goldenTwo",
   2413                   parserOk (ofNumber 2) Leaf)
   2414               , ("main = 2 |> id where id x = x", "goldenTwo",
   2415                   parserOk (ofNumber 2) Leaf)
   2416               , ("main = 2 |> missing", "goldenPipeUnknown", parserErr
   2417                   (portableDiagnostic (ofNumber 20) (ofString "missing")
   2418                     Leaf (gatePosition 1 13 12) Leaf)
   2419                   (gateAst 2 (ofString "missing")
   2420                     (gatePosition 1 13 12) (gatePosition 1 20 19)))
   2421               , ("main = 2 |>", "goldenPipeMissingOperand", parserErr
   2422                   (portableDiagnostic (ofNumber 2) (ofNumber 7)
   2423                     Leaf Leaf Leaf) Leaf)
   2424               , ("f = 2\nf |> let id x = x in id", "goldenTwo",
   2425                   parserOk (ofNumber 2) Leaf)
   2426               , ("main = 2\n  |> (x : x)", "goldenTwo",
   2427                   parserOk (ofNumber 2) Leaf)
   2428               , ("main = 2 |>\n  (x : x)", "goldenTwo",
   2429                   parserOk (ofNumber 2) Leaf)
   2430               , ("main = do bind\n  x <- 2\n  x\nbind a f = f a",
   2431                   "goldenTwo", parserOk (ofNumber 2) Leaf)
   2432               , ("main = do unused\n  2", "goldenTwo",
   2433                   parserOk (ofNumber 2) Leaf)
   2434               , ("main = do bind\n  1\n  2\nbind a f = f a",
   2435                   "goldenTwo", parserOk (ofNumber 2) Leaf)
   2436               , ("main = do bind\n  x <- 2\n  y <- x\n  y\nbind a f = f a",
   2437                   "goldenTwo", parserOk (ofNumber 2) Leaf)
   2438               , ("main = do bind\n  x <- 2\n  do bind\n    y <- x\n    y\nbind a f = f a",
   2439                   "goldenTwo", parserOk (ofNumber 2) Leaf)
   2440               , ("main = do bind\n  x <- (y : y)\n         2\n  x\nbind a f = f a",
   2441                   "goldenTwo", parserOk (ofNumber 2) Leaf)
   2442               , ("main = let _ = 42 in do bind\n  1\n  _\nbind a f = f a",
   2443                   "goldenOne", parserOk (ofNumber 1) Leaf)
   2444               , ("main = do bind\n  x <- 2", "goldenDoMissingFinal", parserErr
   2445                   (portableDiagnostic (ofNumber 40)
   2446                     (ofString "do final expression") Leaf
   2447                     (gatePosition 2 3 17) Leaf) Leaf)
   2448               , ("main = do bind\n  x <- missing\n  x\nbind a f = f a",
   2449                   "goldenDoUnboundAction", parserErr
   2450                     (portableDiagnostic (ofNumber 20) (ofString "missing")
   2451                       Leaf (gatePosition 2 8 22) Leaf)
   2452                     (gateAst 2 (ofString "missing")
   2453                       (gatePosition 2 8 22) (gatePosition 2 15 29)))
   2454               ]
   2455         case decodeBundle bytes of
   2456           Left diagnostic -> assertFailure diagnostic
   2457           Right bundle -> do
   2458             let artifact = reconstructBundleTerms (bundleNodes bundle)
   2459                   V.! fromIntegral (head (bundleRoots bundle))
   2460             forM_ cases $ \(source, goldenName, expected) -> do
   2461               Map.lookup goldenName goldenEnv @?= Just expected
   2462               assertBool ("program golden mismatch: " ++ goldenName
   2463                 ++ " for " ++ show source)
   2464                 (apply (apply artifact (ofString source)) expected == trueT)
   2465             case Map.lookup "goldenProgramCycleWrongPosition" goldenEnv of
   2466               Nothing -> assertFailure "missing program wrong-position control"
   2467               Just wrong ->
   2468                 apply (apply artifact (ofString "a = b\nb = a\nmain = t")) wrong
   2469                   @?= falseT
   2470   , testCase "output goldens match the portable Haskell bundle" $
   2471       withSystemTempDirectory "gate-unit-output-goldens" $ \dir -> do
   2472         let path = dir </> "output-matches.arboricx"
   2473         packCompilerFile
   2474           "./test/sourceGateUnitCompiler.tri" path [pack "matchesOutput"]
   2475         bytes <- BS.readFile path
   2476         goldenEnv <- evaluateCompilerFile "./test/sourceGateExpected.tri"
   2477         case decodeBundle bytes of
   2478           Left diagnostic -> assertFailure diagnostic
   2479           Right bundle -> do
   2480             let artifact = reconstructBundleTerms (bundleNodes bundle)
   2481                   V.! fromIntegral (head (bundleRoots bundle))
   2482                 cases =
   2483                   [ ("main = value\nvalue = t", "goldenOutputForward",
   2484                       parserOk (pairT Leaf (ofList
   2485                         [ pairT (ofString "value") Leaf
   2486                         , pairT (ofString "main") Leaf
   2487                         ])) Leaf)
   2488                   , ("2", "goldenOutputExpression",
   2489                       parserOk (pairT (ofNumber 2) Leaf) Leaf)
   2490                   ]
   2491             forM_ cases $ \(source, goldenName, expected) -> do
   2492               Map.lookup goldenName goldenEnv @?= Just expected
   2493               apply (apply artifact (ofString source)) expected @?= trueT
   2494   , testCase "output includes root and definitions in dependency order" $ do
   2495       let source = "main = value\nvalue = t"
   2496           output = foldl apply (gateUnitEnv Map.! "compileGateProgramOutput") [Leaf, ofString source]
   2497           definitions = ofList
   2498             [ pairT (ofString "value") Leaf
   2499             , pairT (ofString "main") Leaf
   2500             ]
   2501       output @?= parserOk (pairT Leaf definitions) Leaf
   2502       check Leaf source (parserOk Leaf Leaf)
   2503   , testCase "empty output environment still carries the selected root" $
   2504       foldl apply (gateUnitEnv Map.! "compileGateProgramOutput") [Leaf, ofString "t"] @?=
   2505           parserOk (pairT Leaf Leaf) Leaf
   2506   , testCase "output entrypoint preserves structured diagnostics" $
   2507       foldl apply (gateUnitEnv Map.! "compileGateProgramOutput") [Leaf, ofString "helper = t"] @?=
   2508           parserErr (portableDiagnostic (ofNumber 32)
   2509             (ofString "main") Leaf Leaf Leaf) Leaf
   2510   , testCase "public compileSource normalizes with abstract environment and source" $ do
   2511       env <- evaluateCompilerFile
   2512         "./test/sourcePublicCompiler.tri"
   2513       Map.member "matchesSource" env @?= True
   2514       let check environment source expected = do
   2515             foldl apply (fixtureExports "source.unit" Map.! "compileSource")
   2516               [environment, ofString source] @?= expected
   2517             foldl apply (env Map.! "matchesSource")
   2518               [environment, ofString source, expected] @?= trueT
   2519       assertBool "test wrapper leaked imported compiler" (Map.notMember "compileSource" env)
   2520       check Leaf "main = value\nvalue = t"
   2521         (parserOk (pairT Leaf (ofList
   2522           [pairT (ofString "value") Leaf, pairT (ofString "main") Leaf])) Leaf)
   2523       check (ofList [Fork (ofString "host") (Stem Leaf)]) "host"
   2524         (parserOk (pairT (Stem Leaf) Leaf) Leaf)
   2525       check Leaf "helper = t"
   2526         (parserErr (portableDiagnostic (ofNumber 32)
   2527           (ofString "main") Leaf Leaf Leaf) Leaf)
   2528       foldl apply (env Map.! "matchesSource")
   2529         [Leaf, ofString "main = t", parserOk (pairT (Stem Leaf) Leaf) Leaf] @?= falseT
   2530   , testCase "public compileSource is an exportable two-argument bundle" $
   2531       withSystemTempDirectory "public-source-artifact" $ \dir -> do
   2532         let path = dir </> "compile-source.arboricx"
   2533         packCompilerFile
   2534           "./lib/source/unit.tri" path [pack "compileSource"]
   2535         bytes <- BS.readFile path
   2536         case decodeBundle bytes of
   2537           Left diagnostic -> assertFailure diagnostic
   2538           Right bundle -> do
   2539             let artifact = reconstructBundleTerms (bundleNodes bundle)
   2540                   V.! fromIntegral (head (bundleRoots bundle))
   2541                 cases =
   2542                   [ (Leaf, "2", parserOk (pairT (ofNumber 2) Leaf) Leaf)
   2543                   , (Leaf, "main = value\nvalue = t", parserOk
   2544                       (pairT Leaf (ofList [pairT (ofString "value") Leaf,
   2545                         pairT (ofString "main") Leaf])) Leaf)
   2546                   , (ofList [pairT (ofString "host") (Stem Leaf)],
   2547                       "host", parserOk (pairT (Stem Leaf) Leaf) Leaf)
   2548                   , (Leaf, "helper = t", parserErr
   2549                       (portableDiagnostic (ofNumber 32)
   2550                         (ofString "main") Leaf Leaf Leaf) Leaf)
   2551                   , (Leaf, "#", parserErr
   2552                       (portableDiagnostic (ofNumber 4) Leaf (ofNumber 35)
   2553                         (gatePosition 1 1 0) Leaf) (ofString "#"))
   2554                   , (Leaf, "!import \"x\" NS", parserErr
   2555                       (portableDiagnostic (ofNumber 4) Leaf (ofNumber 33)
   2556                         (gatePosition 1 1 0) Leaf)
   2557                       (ofString "!import \"x\" NS"))
   2558                   ]
   2559             forM_ cases $ \(environment, source, expected) -> do
   2560               let compiled = apply (apply artifact environment) (ofString source)
   2561               compiled @?= expected
   2562               -- Repeated fixed inputs have structurally identical complete
   2563               -- outputs, not merely the same selected root.
   2564               apply (apply artifact environment) (ofString source) @?= compiled
   2565             forM_
   2566               [ ("./test/fork.tri", Fork Leaf Leaf)
   2567               , ("./test/comments-1.tri", Fork (Stem Leaf) Leaf)
   2568               ] $ \(fixture, root) -> do
   2569                 source <- readFile fixture
   2570                 apply (apply artifact Leaf) (ofString source) @?=
   2571                   parserOk (pairT root
   2572                     (ofList [pairT (ofString "main") root])) Leaf
   2573             let externalNames = ["map", "append", "equal?", "head", "y", "triage"]
   2574                 external = ofList
   2575                   [ pairT (ofString name) (fixtureExports "base" Map.! name)
   2576                   | name <- externalNames ]
   2577             stringSource <- readFile "./test/string.tri"
   2578             apply (apply artifact external) (ofString stringSource) @?=
   2579               parserOk (pairT (ofString "String test!") Leaf) Leaf
   2580             mapSource <- readFile "./test/map.tri"
   2581             apply (apply artifact external) (ofString mapSource) @?=
   2582               parserOk (pairT trueT (ofList
   2583                 [ pairT (ofString "x")
   2584                     (ofList [ofString "Successfully concatenated two strings!"])
   2585                 , pairT (ofString "main") trueT
   2586                 ])) Leaf
   2587   , testCase "public bundle functions agree by application, not tree shape" $
   2588       withSystemTempDirectory "public-source-functions" $ \dir -> do
   2589         let path = dir </> "compile-source.arboricx"
   2590         packCompilerFile
   2591           "./lib/source/unit.tri" path [pack "compileSource"]
   2592         bytes <- BS.readFile path
   2593         comparatorEnv <- evaluateCompilerFile "./test/sourcePublicCompiler.tri"
   2594         case decodeBundle bytes of
   2595           Left diagnostic -> assertFailure diagnostic
   2596           Right bundle -> do
   2597             let compiler = reconstructBundleTerms (bundleNodes bundle)
   2598                   V.! fromIntegral (head (bundleRoots bundle))
   2599                 comparator = comparatorEnv Map.! "matchesSourceApplicationArtifact"
   2600                 cases =
   2601                   [ ("main x = x", ofNumber 2, ofNumber 2)
   2602                   , ("main x = t", Stem Leaf, Leaf)
   2603                   , ("main x = let id a = a in id x", Stem Leaf, Stem Leaf)
   2604                   , ("main x = do bind\n  y <- x\n  y\nbind a f = f a",
   2605                       ofNumber 2, ofNumber 2)
   2606                   ]
   2607             forM_ cases $ \(source, input, expected) -> do
   2608               foldl apply comparator
   2609                 [compiler, Leaf, ofString source, input, expected] @?= trueT
   2610             let rootComparator = comparatorEnv Map.! "matchesSourceRootArtifact"
   2611                 unused = "unused = (x : x x) (x : x x)\nmain = t"
   2612             unusedRoot <- Timeout.timeout (30 * 1000000) $
   2613               evaluate (foldl apply rootComparator
   2614                 [compiler, Leaf, ofString unused, Leaf] == trueT)
   2615             unusedRoot @?= Just True
   2616   , testCase "self-built compiler wrapper handles a forward reference" $ do
   2617       env <- evaluateCompilerFile
   2618         "./test/sourceGateUnitImport.tri"
   2619       Map.lookup "main" env @?= Just (parserOk Leaf Leaf)
   2620   , testCase "forward definitions with arguments evaluate before main" $ do
   2621       let source = "main = id 42\nid x = x"
   2622       check Leaf source (parserOk (ofNumber 42) Leaf)
   2623   , testCase "final expression selects root in absence of main" $
   2624       check Leaf "id x = x\nid t" (parserOk Leaf Leaf)
   2625   , testCase "a local main wins over a final expression" $
   2626       check Leaf "main = 2\n3" (parserOk (ofNumber 2) Leaf)
   2627   , testCase "provided environment is visible inside definitions" $
   2628       check (ofList [Fork (ofString "host") (Stem Leaf)]) "main = host t"
   2629         (parserOk (Fork Leaf Leaf) Leaf)
   2630   , testCase "multiple definition arguments preserve binder order" $
   2631       let source = "main x y = x"
   2632           program = foldl apply (gateUnitEnv Map.! "compileGateProgram") [Leaf, ofString source]
   2633       in case program of
   2634            Fork tag (Fork fun Leaf) | tag == trueT ->
   2635              apply (apply fun Leaf) (Stem Leaf) @?= Leaf
   2636            other -> assertFailure ("Expected compiled two-argument function, got " ++ show other)
   2637   , testCase "recursive top-level function and triage compile from source" $ do
   2638       let source = "triage leaf stem fork = t (t leaf stem) fork\n"
   2639             ++ "main xs = y (self : rest : triage t (h : self h) (a : b : self a) rest) xs"
   2640           external = ofList [Fork (ofString "y") (gateCompileBaseEnv Map.! "y")]
   2641           compiled = foldl apply (gateUnitEnv Map.! "compileGateProgram") [external, ofString source]
   2642       case compiled of
   2643         Fork tag (Fork fun Leaf) | tag == trueT -> do
   2644           apply fun Leaf @?= Leaf
   2645           apply fun (Stem Leaf) @?= Leaf
   2646           apply fun (Fork (Stem Leaf) Leaf) @?= Leaf
   2647         other -> assertFailure ("Expected recursive source function, got " ++ show other)
   2648   , testCase "recursive function, y, and triage compile entirely from source" $ do
   2649       let source = "y = ((mut : wait : fun : wait mut (x : fun (wait mut x))) "
   2650             ++ "(x : x x) (a0 : a1 : a2 : t (t a0) (t t a2) a1))\n"
   2651             ++ "triage leaf stem fork = t (t leaf stem) fork\n"
   2652             ++ "main xs = y (self : rest : triage t (h : self h) "
   2653             ++ "(a : b : self a) rest) xs"
   2654           compiled = foldl apply (gateUnitEnv Map.! "compileGateProgram") [Leaf, ofString source]
   2655       case compiled of
   2656         Fork tag (Fork fun Leaf) | tag == trueT -> do
   2657           apply fun Leaf @?= Leaf
   2658           apply fun (Stem Leaf) @?= Leaf
   2659           apply fun (Fork (Stem Leaf) Leaf) @?= Leaf
   2660         other -> assertFailure ("Expected fully sourced recursive function, got " ++ show other)
   2661   , testCase "indented definition bodies preserve continuation layout" $ do
   2662       let sources =
   2663             [ ("main =\n  t", Leaf)
   2664             , ("main =\nt", Leaf)
   2665             , ("main =\n  (x : x)\n    2", ofNumber 2)
   2666             , ("main =\n  id\n    t\nid x = x", Leaf)
   2667             , ("main = value\nwhere value = t", Leaf)
   2668             , ("main =\n  value\n  where value = t", Leaf)
   2669             ]
   2670       forM_ sources $ \(source, value) -> do
   2671         check Leaf source (parserOk value Leaf)
   2672   , testCase "multiline grouped applications retain expected results" $ do
   2673       let sources =
   2674             [ ("main = (t\n t)", Stem Leaf)
   2675             , ("main = [(t\n t)]", ofList [Stem Leaf])
   2676             , ("main = (let x = t in x\n t)", Stem Leaf)
   2677             , ("main = (x\n where x = t)", Leaf)
   2678             ]
   2679       forM_ sources $ \(source, value) -> do
   2680         check Leaf source (parserOk value Leaf)
   2681   , testCase "an unindented do block has a located diagnostic" $
   2682       let diagnostic = portableDiagnostic (ofNumber 40)
   2683             (ofString "indented do block") (ofNumber 2)
   2684             (gatePosition 2 1 15) Leaf
   2685           reply = apply (gateProgramEnv Map.! "parseGateProgram")
   2686             (ofString "main = do bind\nx <- 2\n  x")
   2687       in apply (fixtureExports "parser" Map.! "parserReplyPayload") reply @?= diagnostic
   2688   , testCase "pipe followed by a local assignment stays a final expression" $
   2689       check Leaf "f = 2\nf |> let id x = x in id"
   2690         (parserOk (ofNumber 2) Leaf)
   2691   , testCase "explicit-bind do with nested statements retains expected results" $ do
   2692       let sources =
   2693             [ "main = do bind\n  x <- 2\n  x\nbind a f = f a"
   2694             , "main = do unused\n  2"
   2695             , "main = do bind\n  1\n  2\nbind a f = f a"
   2696             , "main = do bind\n  x <- 2\n  y <- x\n  y\nbind a f = f a"
   2697             , "main = do bind\n  x <- 2\n  do bind\n    y <- x\n    y\nbind a f = f a"
   2698             ]
   2699       forM_ sources $ \source -> do
   2700         check Leaf source (parserOk (ofNumber 2) Leaf)
   2701   , testCase "pipe directions, precedence, and local names retain expected results" $ do
   2702       let sources =
   2703             [ ("main = 2 |> (x : x)", ofNumber 2)
   2704             , ("main = (x : x) <| 2", ofNumber 2)
   2705             , ("main = 2 |> (x : x) |> (x : x)", ofNumber 2)
   2706             , ("main = t <| t t", Stem (Stem Leaf))
   2707             , ("main = 2 |> id\nid x = x", ofNumber 2)
   2708             , ("main = 2 |> id where id x = x", ofNumber 2)
   2709             , ("main = 2\n  |> (x : x)", ofNumber 2)
   2710             , ("main = 2 |>\n  (x : x)", ofNumber 2)
   2711             ]
   2712       forM_ sources $ \(source, value) -> do
   2713         check Leaf source (parserOk value Leaf)
   2714   , testCase "multiline local let/where layout retains expected results" $ do
   2715       let sources =
   2716             [ ("main = let x =\n  2\nin x", ofNumber 2)
   2717             , ("main = let x = t in\n  x", Leaf)
   2718             , ("main = let\n  id x =\n    x\nin id 2", ofNumber 2)
   2719             , ("main =\n  let x =\n    t\n  in x", Leaf)
   2720             , ("main = t where x =\n  2", Leaf)
   2721             , ("main = id 2 where\n  id x =\n    x", ofNumber 2)
   2722             , ("main = let x =\n  (y : y)\n    2\nin x", ofNumber 2)
   2723             , ("main = let id x = x in\n  id\n    2", ofNumber 2)
   2724             ]
   2725       forM_ sources $ \(source, value) -> do
   2726         check Leaf source (parserOk value Leaf)
   2727   , testCase "local bindings preserve lexical scope and argument order" $ do
   2728       let sources =
   2729             [ ("main = let x = 2 in x", ofNumber 2)
   2730             , ("main = let id x = x in id 2", ofNumber 2)
   2731             , ("main = id 2 where id x = x", ofNumber 2)
   2732             , ("result where result = t", Leaf)
   2733             , ("main = let x = t in (y : x) 2", Leaf)
   2734             ]
   2735       forM_ sources $ \(source, value) -> do
   2736         check Leaf source (parserOk value Leaf)
   2737   , testCase "local binding value has no self-reference" $
   2738       check Leaf "main = let x = x in x" (parserErr
   2739         (portableDiagnostic (ofNumber 20) (ofString "x") Leaf
   2740           (gatePosition 1 16 15) Leaf)
   2741         (gateAst 2 (ofString "x")
   2742           (gatePosition 1 16 15) (gatePosition 1 17 16)))
   2743   , testCase "a local binder shadows a top-level definition" $
   2744       check Leaf "main = local where local = t\nlocal = 2"
   2745         (parserOk Leaf Leaf)
   2746   , testCase "local free names order forward-referenced definitions" $
   2747       check Leaf "main = value where value = helper t\nhelper x = x"
   2748         (parserOk Leaf Leaf)
   2749   , testCase "lists may refer to forward definitions" $
   2750       check Leaf "main = [(id 2)]\nid x = x"
   2751         (parserOk (ofList [ofNumber 2]) Leaf)
   2752   , testCase "unused divergent body is checked but not demanded" $ do
   2753       let source = "unused = (x : x x) (x : x x)\nmain = t"
   2754       check Leaf source (parserOk Leaf Leaf)
   2755   , testCase "missing multiline definition body is a structured diagnostic" $
   2756       check Leaf "main =\n" (parserErr
   2757         (portableDiagnostic (ofNumber 40) (ofString "definition body")
   2758           Leaf Leaf Leaf) Leaf)
   2759   , testCase "missing local value or body returns a structured diagnostic" $ do
   2760       check Leaf "main = let x =\n" (parserErr
   2761         (portableDiagnostic (ofNumber 40)
   2762           (ofString "local binding value") Leaf Leaf Leaf) Leaf)
   2763       check Leaf "main = let x = t in\n" (parserErr
   2764         (portableDiagnostic (ofNumber 40)
   2765           (ofString "local binding body") Leaf Leaf Leaf) Leaf)
   2766   , testCase "no root is a structured diagnostic" $
   2767       check Leaf "helper = t" (parserErr
   2768         (portableDiagnostic (ofNumber 32) (ofString "main") Leaf Leaf Leaf)
   2769         Leaf)
   2770   , testCase "cycle fails before result selection" $
   2771       check Leaf "a = b\nb = a\nmain = t" (parserErr
   2772         (portableDiagnostic (ofNumber 31) (ofString "acyclic")
   2773           (ofString "a") (gatePosition 1 1 0)
   2774           (ofList [ofString "a"])) Leaf)
   2775   ]
   2776   where
   2777     check external source expected =
   2778       foldl apply (gateUnitEnv Map.! "compileGateProgram") [external, ofString source]
   2779         @?= expected
   2780 
   2781 -- --------------------------------------------------------------------------
   2782 -- Single-expression feasibility-gate name resolution
   2783 -- --------------------------------------------------------------------------
   2784 
   2785 gateResolveEnv :: Env
   2786 gateResolveEnv = fixtureExports "source.resolve"
   2787 {-# NOINLINE gateResolveEnv #-}
   2788 
   2789 gateCore :: Integer -> T -> T -> T -> T
   2790 gateCore = gateAst
   2791 
   2792 gateResolutionTests :: TestTree
   2793 gateResolutionTests = testGroup "Feasibility-gate resolution"
   2794   [ testCase "self-built resolver export executes through its wrapper" $ do
   2795       env <- evaluateCompilerFile "./test/sourceGateResolveImport.tri"
   2796       let bound = gateCore 2 (ofNumber 0) (gatePosition 1 5 4) (gatePosition 1 6 5)
   2797           lam = gateCore 4 bound (gatePosition 1 1 0) (gatePosition 1 6 5)
   2798       Map.lookup "main" env @?= Just (parserOk lam Leaf)
   2799   , testCase "nearest lexical binder has index zero" $
   2800       let bound = gateCore 2 (ofNumber 0) (gatePosition 1 5 4) (gatePosition 1 6 5)
   2801       in check Leaf "x : x" (parserOk
   2802         (gateCore 4 bound (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf)
   2803   , testCase "outer lexical binder has index one" $
   2804       let bound = gateCore 2 (ofNumber 1) (gatePosition 1 9 8) (gatePosition 1 10 9)
   2805           inner = gateCore 4 bound (gatePosition 1 5 4) (gatePosition 1 10 9)
   2806       in check Leaf "x : y : x" (parserOk
   2807         (gateCore 4 inner (gatePosition 1 1 0) (gatePosition 1 10 9)) Leaf)
   2808   , testCase "local binding lowers to an applied scoped lambda" $
   2809       let value = gateCore 1 Leaf
   2810             (gatePosition 1 9 8) (gatePosition 1 10 9)
   2811           bound = gateCore 2 (ofNumber 0)
   2812             (gatePosition 1 14 13) (gatePosition 1 15 14)
   2813           scoped = gateCore 4 bound
   2814             (gatePosition 1 1 0) (gatePosition 1 15 14)
   2815       in check Leaf "let x = t in x" (parserOk
   2816         (gateCore 3 (pairT scoped value)
   2817           (gatePosition 1 1 0) (gatePosition 1 15 14)) Leaf)
   2818   , testCase "forward pipe elaborates to reversed core application" $
   2819       let function = gateCore 5 (Stem Leaf)
   2820             (gatePosition 1 6 5) (gatePosition 1 7 6)
   2821           argument = gateCore 6 (ofNumber 2)
   2822             (gatePosition 1 1 0) (gatePosition 1 2 1)
   2823       in check (ofList [Fork (ofString "f") (Stem Leaf)]) "2 |> f" (parserOk
   2824         (gateCore 3 (pairT function argument)
   2825           (gatePosition 1 1 0) (gatePosition 1 7 6)) Leaf)
   2826   , testCase "number becomes a resolved tree literal" $
   2827       check Leaf "42" (parserOk
   2828         (gateCore 6 (ofNumber 42) (gatePosition 1 1 0) (gatePosition 1 3 2)) Leaf)
   2829   , testCase "string becomes a resolved tree literal" $
   2830       check Leaf "\"hi\"" (parserOk
   2831         (gateCore 7 (ofString "hi") (gatePosition 1 1 0) (gatePosition 1 5 4)) Leaf)
   2832   , testCase "list elements become individually resolved core nodes" $
   2833       let leaf = gateCore 1 Leaf (gatePosition 1 2 1) (gatePosition 1 3 2)
   2834           number = gateCore 6 (ofNumber 2)
   2835             (gatePosition 1 4 3) (gatePosition 1 5 4)
   2836       in check Leaf "[t 2]" (parserOk
   2837         (gateCore 8 (ofList [leaf, number])
   2838           (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf)
   2839   , testCase "unresolved list element preserves its own span" $
   2840       check Leaf "[u]" (parserErr
   2841         (portableDiagnostic (ofNumber 20) (ofString "u") Leaf
   2842           (gatePosition 1 2 1) Leaf)
   2843         (gateAst 2 (ofString "u")
   2844           (gatePosition 1 2 1) (gatePosition 1 3 2)))
   2845   , testCase "supplied external tree becomes a core value" $
   2846       let external = gateCore 5 (Stem Leaf)
   2847             (gatePosition 1 1 0) (gatePosition 1 2 1)
   2848           leaf = gateCore 1 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3)
   2849       in check (ofList [Fork (ofString "f") (Stem Leaf)]) "f t" (parserOk
   2850         (gateCore 3 (pairT external leaf)
   2851           (gatePosition 1 1 0) (gatePosition 1 4 3)) Leaf)
   2852   , testCase "lexical binder shadows an external value" $
   2853       let bound = gateCore 2 (ofNumber 0) (gatePosition 1 5 4) (gatePosition 1 6 5)
   2854       in check (ofList [Fork (ofString "x") (Stem Leaf)]) "x : x" (parserOk
   2855         (gateCore 4 bound (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf)
   2856   , testCase "unresolved name reports its original position" $
   2857       check Leaf "u" (parserErr
   2858         (portableDiagnostic (ofNumber 20) (ofString "u") Leaf
   2859           (gatePosition 1 1 0) Leaf)
   2860         (gateAst 2 (ofString "u") (gatePosition 1 1 0) (gatePosition 1 2 1)))
   2861   , testCase "grouping carries its span into the core" $
   2862       check Leaf "(t)" (parserOk
   2863         (gateCore 1 Leaf (gatePosition 1 1 0) (gatePosition 1 4 3)) Leaf)
   2864   ]
   2865   where
   2866     check environment input expected =
   2867       foldl apply (gateResolveEnv Map.! "resolveGateSource") [environment, ofString input] @?= expected
   2868 
   2869 -- --------------------------------------------------------------------------
   2870 -- Reference S/K/I compiler for the feasibility gate
   2871 -- --------------------------------------------------------------------------
   2872 
   2873 gateCompileEnv :: Env
   2874 gateCompileEnv = fixtureExports "source.compile"
   2875 {-# NOINLINE gateCompileEnv #-}
   2876 
   2877 gateCompileBaseEnv :: Env
   2878 gateCompileBaseEnv = fixtureExports "base"
   2879 {-# NOINLINE gateCompileBaseEnv #-}
   2880 
   2881 gateCompilationTests :: TestTree
   2882 gateCompilationTests = testGroup "Feasibility-gate source compiler"
   2883   [ testCase "Haskell reducer executes the exported conformance artifact" $
   2884       withSystemTempDirectory "gate-conforms-bundle" $ \dir -> do
   2885         let path = dir </> "conforms.arboricx"
   2886         packCompilerFile "./test/sourceGateCompiler.tri"
   2887           path [pack "conforms"]
   2888         bytes <- BS.readFile path
   2889         case decodeBundle bytes of
   2890           Left diagnostic -> assertFailure diagnostic
   2891           Right bundle -> do
   2892             let artifact = reconstructBundleTerms (bundleNodes bundle)
   2893                   V.! fromIntegral (head (bundleRoots bundle))
   2894                 corpus =
   2895                   [ ("t", "t", trueT)
   2896                   , ("t t", "t", falseT)
   2897                   , ("t t t", "t t t", trueT)
   2898                   , ("(x : x) t", "t", trueT)
   2899                   , ("(x : t) (t t)", "t", trueT)
   2900                   , ("u", "u", trueT)
   2901                   , ("u", "t", falseT)
   2902                   , (nestedIdentity 16, "t", trueT)
   2903                   , (nestedBinders 16, "t", trueT)
   2904                   ]
   2905             forM_ corpus $ \(source, expectedSource, expected) ->
   2906               apply (apply artifact (ofString source)) (ofString expectedSource)
   2907                 @?= expected
   2908   , testCase "independent golden results match the Haskell artifact" $
   2909       withSystemTempDirectory "gate-golden-bundle" $ \dir -> do
   2910         let path = dir </> "matches.arboricx"
   2911         packCompilerFile "./test/sourceGateCompiler.tri"
   2912           path [pack "matchesExpected"]
   2913         bytes <- BS.readFile path
   2914         goldenEnv <- evaluateCompilerFile "./test/sourceGateExpected.tri"
   2915         let unknown = parserErr
   2916               (portableDiagnostic (ofNumber 20) (ofString "u") Leaf
   2917                 (gatePosition 1 1 0) Leaf)
   2918               (gateAst 2 (ofString "u") (gatePosition 1 1 0) (gatePosition 1 2 1))
   2919             unsupported = parserErr
   2920               (portableDiagnostic (ofNumber 4) Leaf (ofNumber 35)
   2921                 (gatePosition 1 1 0) Leaf)
   2922               (ofString "#")
   2923             badEscape = parserErr
   2924               (portableDiagnostic (ofNumber 6) (ofNumber 34) (ofNumber 113)
   2925                 (gatePosition 1 3 2) Leaf)
   2926               (ofString "\\q\"")
   2927             unfinishedString = parserErr
   2928               (portableDiagnostic (ofNumber 5) (ofNumber 34) Leaf
   2929                 (gatePosition 1 1 0) Leaf) Leaf
   2930             unfinishedBlock = parserErr
   2931               (portableDiagnostic (ofNumber 5) (ofString "-|") Leaf
   2932                 (gatePosition 1 1 0) Leaf) Leaf
   2933             unknownInList = parserErr
   2934               (portableDiagnostic (ofNumber 20) (ofString "u") Leaf
   2935                 (gatePosition 1 2 1) Leaf)
   2936               (gateAst 2 (ofString "u")
   2937                 (gatePosition 1 2 1) (gatePosition 1 3 2))
   2938             missingListClose = parserErr
   2939               (portableDiagnostic (ofNumber 2) (ofNumber 10) Leaf Leaf Leaf)
   2940               Leaf
   2941             comma = parserErr
   2942               (portableDiagnostic (ofNumber 2) (ofNumber 10) (ofNumber 20)
   2943                 (gatePosition 1 3 2) Leaf)
   2944               (ofList
   2945                 [ gateToken 20 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3)
   2946                 , gateToken 1 (ofString "t") (gatePosition 1 5 4) (gatePosition 1 6 5)
   2947                 , gateToken 10 Leaf (gatePosition 1 6 5) (gatePosition 1 7 6)
   2948                 ])
   2949             golden =
   2950               [ ("t", "goldenLeaf", parserOk Leaf Leaf)
   2951               , ("t t", "goldenStem", parserOk (Stem Leaf) Leaf)
   2952               , ("t t t", "goldenFork", parserOk (Fork Leaf Leaf) Leaf)
   2953               , ("(x : x) t", "goldenLeaf", parserOk Leaf Leaf)
   2954               , ("42", "goldenFortyTwo", parserOk (ofNumber 42) Leaf)
   2955               , ("\"hi\"", "goldenHi", parserOk (ofString "hi") Leaf)
   2956               , ("|-note-| \"hi\"", "goldenHi", parserOk (ofString "hi") Leaf)
   2957               , ("u", "goldenUnknown", unknown)
   2958               , ("#", "goldenUnsupported", unsupported)
   2959               , ("\"\\q\"", "goldenBadEscape", badEscape)
   2960               , ("\"hi", "goldenUnfinishedString", unfinishedString)
   2961               , ("|-oops", "goldenUnfinishedBlock", unfinishedBlock)
   2962               , ("[]", "goldenLeaf", parserOk Leaf Leaf)
   2963               , ("[t 2]", "goldenListTwo",
   2964                   parserOk (ofList [Leaf, ofNumber 2]) Leaf)
   2965               , ("[t\n2]", "goldenListTwo",
   2966                   parserOk (ofList [Leaf, ofNumber 2]) Leaf)
   2967               , ("[[t] (t t)]", "goldenNestedList",
   2968                   parserOk (ofList [ofList [Leaf], Stem Leaf]) Leaf)
   2969               , ("[u]", "goldenUnknownInList", unknownInList)
   2970               , ("[t", "goldenMissingListClose", missingListClose)
   2971               , ("[t, t]", "goldenComma", comma)
   2972               ]
   2973         case decodeBundle bytes of
   2974           Left diagnostic -> assertFailure diagnostic
   2975           Right bundle -> do
   2976             let artifact = reconstructBundleTerms (bundleNodes bundle)
   2977                   V.! fromIntegral (head (bundleRoots bundle))
   2978             forM_ golden $ \(source, name, expected) -> do
   2979               Map.lookup name goldenEnv @?= Just expected
   2980               apply (apply artifact (ofString source)) expected @?= trueT
   2981             case Map.lookup "goldenUnknownWrongPosition" goldenEnv of
   2982               Nothing -> assertFailure "missing incorrect-position control"
   2983               Just wrong -> do
   2984                 apply (apply artifact (ofString "u")) wrong @?= falseT
   2985                 apply (apply artifact (ofString "t t"))
   2986                   (parserOk Leaf Leaf) @?= falseT
   2987   , testCase "self-built resolver export executes through its wrapper" $ do
   2988       env <- evaluateCompilerFile "./test/sourceGateCompileImport.tri"
   2989       Map.lookup "main" env @?= Just (parserOk Leaf Leaf)
   2990   , testCase "leaf compiles to raw Leaf" $
   2991       check "t" (parserOk Leaf Leaf)
   2992   , testCase "decimal literal compiles to the canonical number tree" $ do
   2993       check "42" (parserOk (ofNumber 42) Leaf)
   2994       check "000" (parserOk Leaf Leaf)
   2995   , testCase "list element boundaries distinguish atoms from grouped applications" $ do
   2996       check "[t t]" (parserOk (ofList [Leaf, Leaf]) Leaf)
   2997       check "[(t t)]" (parserOk (ofList [Stem Leaf]) Leaf)
   2998       check "[t\n2]" (parserOk (ofList [Leaf, ofNumber 2]) Leaf)
   2999   , testCase "lists compile to canonical Fork cells" $ do
   3000       check "[]" (parserOk Leaf Leaf)
   3001       check "[t 2]" (parserOk (ofList [Leaf, ofNumber 2]) Leaf)
   3002       check "[[t] (t t)]" (parserOk
   3003         (ofList [ofList [Leaf], Stem Leaf]) Leaf)
   3004       check "[t\n2]" (parserOk (ofList [Leaf, ofNumber 2]) Leaf)
   3005       check "(x : [x]) t" (parserOk (ofList [Leaf]) Leaf)
   3006   , testCase "string literal compiles to the canonical character list" $ do
   3007       check "\"hi\"" (parserOk (ofString "hi") Leaf)
   3008       check "(x : x) \"hi\"" (parserOk (ofString "hi") Leaf)
   3009   , testCase "comments and literals compose without a special host lexer" $ do
   3010       check "42 --ok" (parserOk (ofNumber 42) Leaf)
   3011       check "|-note-| \"hi\"" (parserOk (ofString "hi") Leaf)
   3012   , testCase "application evaluates to raw Stem and Fork" $ do
   3013       check "t t" (parserOk (Stem Leaf) Leaf)
   3014       check "t t t" (parserOk (Fork Leaf Leaf) Leaf)
   3015   , testCase "identity behaves correctly under arguments" $
   3016       checkFunction "x : x" [ofNumber 5] (ofNumber 5)
   3017   , testCase "constant behaves correctly under arguments" $
   3018       checkFunction "x : t" [ofNumber 5] Leaf
   3019   , testCase "nested lambdas preserve the outer argument" $
   3020       checkFunction "x : y : x" [ofNumber 5, ofNumber 8] (ofNumber 5)
   3021   , testCase "pruned bracket agrees with the retained reference" $ do
   3022       let term kind payload = Fork (ofNumber kind) (Fork payload Leaf)
   3023           var = term 2 Leaf
   3024           leaf = term 1 Leaf
   3025           app f x = term 3 (Fork f x)
   3026           terms = [var, leaf, app var leaf, app leaf var, app var var]
   3027           compiled method value = apply (gateCompileEnv Map.! "sourceRunTerm")
   3028             (apply (gateCompileEnv Map.! method) value)
   3029       forM_ terms $ \term ->
   3030         case (compiled "sourceBracketReference" term,
   3031               compiled "sourceBracket" term) of
   3032           (Fork referenceTag (Fork reference Leaf),
   3033            Fork optimizedTag (Fork optimized Leaf))
   3034             | referenceTag == trueT && optimizedTag == trueT ->
   3035                 forM_ [Leaf, Stem Leaf] $ \argument ->
   3036                   apply optimized argument @?= apply reference argument
   3037           other -> assertFailure ("Expected two bracketed trees, got " ++ show other)
   3038   , testCase "star eta and retained B/C reference agree when applied" $ do
   3039       let compiled method source =
   3040             case foldl apply (gateResolveEnv Map.! "resolveGateSource") [Leaf, ofString source] of
   3041               Fork tag (Fork core Leaf) | tag == trueT -> apply (gateCompileEnv Map.! method) core
   3042               other -> error ("reference corpus failed resolution: " ++ show other)
   3043           corpus =
   3044             [ ("x : x", [ofNumber 5], ofNumber 5)
   3045             , ("x y : x", [ofNumber 5, ofNumber 8], ofNumber 5)
   3046             , ("x y : y", [ofNumber 5, ofNumber 8], ofNumber 8)
   3047             , ("x : x x", [Stem Leaf], Fork Leaf (Stem Leaf))
   3048             , ("f x : f x", [Stem Leaf, Leaf], Fork Leaf Leaf)
   3049             , ("f x y : f (x y)", [Stem Leaf, Stem Leaf, Leaf],
   3050                 Fork Leaf (Fork Leaf Leaf))
   3051             , ("x : [x 2]", [Leaf], ofList [Leaf, ofNumber 2])
   3052             , ("a : (a : a)", [Leaf, Stem Leaf], Stem Leaf)
   3053             ]
   3054       forM_ corpus $ \(source, arguments, expected) ->
   3055         forM_ ["sourceCompileCoreReference", "sourceCompileCore",
   3056                 "sourceCompileCoreKiselyov"] $ \method ->
   3057           case compiled method source of
   3058             Fork tag (Fork fn Leaf) | tag == trueT ->
   3059               foldl apply fn arguments @?= expected
   3060             other -> assertFailure ("Failed to compile " ++ method ++ " "
   3061               ++ source ++ ": " ++ show other)
   3062   , testCase "application inside a lambda behaves correctly" $
   3063       checkFunction "f : x : f x" [Stem Leaf, ofNumber 8] (Fork Leaf (ofNumber 8))
   3064   , testCase "external tree is applied without host lookup" $
   3065       foldl apply (gateCompileEnv Map.! "compileGateSource")
   3066         [ofList [Fork (ofString "f") (Stem Leaf)], ofString "f t"] @?=
   3067           parserOk (Fork Leaf Leaf) Leaf
   3068   , testCase "backend compiles a recursive source function" $ do
   3069       let source = "xs : y (self : rest : triage t (h : self h) (a : b : self a) rest) xs"
   3070           external = ofList [Fork (ofString name) (gateCompileBaseEnv Map.! name)
   3071                             | name <- ["y", "triage"]]
   3072           compiled = foldl apply (gateCompileEnv Map.! "compileGateSource") [external, ofString source]
   3073       case compiled of
   3074         Fork tag (Fork compiled Leaf) | tag == trueT -> do
   3075           apply compiled Leaf @?= Leaf
   3076           apply compiled (Stem Leaf) @?= Leaf
   3077           apply compiled (Fork (Stem Leaf) Leaf) @?= Leaf
   3078         other -> assertFailure ("Expected compiled recursive function, got " ++ show other)
   3079   , testCase "unresolved name stays a diagnostic" $
   3080       check "u" (parserErr
   3081         (portableDiagnostic (ofNumber 20) (ofString "u") Leaf
   3082           (gatePosition 1 1 0) Leaf)
   3083         (gateAst 2 (ofString "u") (gatePosition 1 1 0) (gatePosition 1 2 1)))
   3084   ]
   3085   where
   3086     nestedIdentity depth = iterate (\body -> "(x : x) (" ++ body ++ ")") "t" !! depth
   3087     nestedBinders depth = "(" ++ concatMap (\i -> "x" ++ show i ++ " : ")
   3088       [0 .. depth - 1] ++ "x0) " ++ unwords (replicate depth "t")
   3089     compile input = foldl apply (gateCompileEnv Map.! "compileGateSource") [Leaf, ofString input]
   3090     check input expected = compile input @?= expected
   3091     checkFunction input args expected = case compile input of
   3092       Fork tag (Fork fn Leaf) | tag == trueT -> foldl apply fn args @?= expected
   3093       other -> assertFailure ("Expected compiled function, got " ++ show other)
   3094 
   3095 -- --------------------------------------------------------------------------
   3096 -- Binary parser combinator tests
   3097 -- --------------------------------------------------------------------------
   3098 
   3099 parserOk :: T -> T -> T
   3100 parserOk val rest = Fork trueT (Fork val rest)
   3101 
   3102 parserErr :: T -> T -> T
   3103 parserErr code rest = Fork falseT (Fork code rest)
   3104 
   3105 -- --------------------------------------------------------------------------
   3106 -- IO driver tests
   3107 -- --------------------------------------------------------------------------
   3108 
   3109 -- A deliberately small interpreter: it records every read request, even if
   3110 -- the supplied host reply is a failure. Unknown effects fail the test rather
   3111 -- than being silently ignored. No filesystem or socket effects are executed.
   3112 runRegistryEffects :: T -> T -> IO ([String], T)
   3113 runRegistryEffects readReply = go (100 :: Int)
   3114   where
   3115     go 0 _ = assertFailure "registry effect trace exceeded fuel" >> pure ([], Leaf)
   3116     go fuel (Fork tag payload) = case toNumber tag of
   3117       Right 0 -> pure ([], payload)
   3118       Right 1 -> case payload of
   3119         Fork action continuation -> do
   3120           (before, value) <- go (fuel - 1) action
   3121           (after, answer) <- go (fuel - 1) (apply continuation value)
   3122           pure (before ++ after, answer)
   3123         _ -> assertFailure "malformed Bind" >> pure ([], Leaf)
   3124       Right 20 -> case toString payload of
   3125         Right path -> pure ([path], readReply)
   3126         Left message -> assertFailure message >> pure ([], Leaf)
   3127       other -> assertFailure ("unexpected registry effect: " ++ show other) >> pure ([], Leaf)
   3128     go _ _ = assertFailure "malformed registry action" >> pure ([], Leaf)
   3129 
   3130 registryContractTests :: TestTree
   3131 registryContractTests = testCase "Registry hash contract: values, bounded rejection and read effects" $
   3132   withSystemTempDirectory "registry-contract" $ \dir -> do
   3133     env <- evaluateCompilerFile "./lib/arboricx/server.tri"
   3134     let call name = foldl apply (env Map.! name)
   3135         http name = foldl apply (fixtureExports "http" Map.! name)
   3136         valid = concat (replicate 4 "0123456789abcdef")
   3137         hash = ofString valid
   3138         diagnostic = ofString "expected 64 lowercase hexadecimal hash characters"
   3139         ok value rest = Fork (Stem Leaf) (Fork value rest)
   3140         err value rest = Fork Leaf (Fork value rest)
   3141         root = ofString "/registry"
   3142         expectedPath = "/registry/objects/012/" ++ valid ++ ".arboricx"
   3143         invalid = ["", replicate 63 'a', replicate 65 'a', replicate 64 'A',
   3144                    'g' : tail valid, take 32 valid ++ "g" ++ drop 33 valid,
   3145                    take 63 valid ++ "g", "../secret", "abc/../../secret",
   3146                    take 63 valid ++ "/", replicate 65536 'a']
   3147     -- All host byte values, not only selected examples.
   3148     forM_ [0..255] $ \byte -> do
   3149       let expected = if byte `elem` map (toInteger . fromEnum) "0123456789abcdef"
   3150                      then Stem Leaf else Leaf
   3151       call "bundleHexByte?" [ofNumber byte] @?= expected
   3152     forM_ ("0123456789abcdef" :: String) $ \digit ->
   3153       call "bundleHashValid?" [ofString (replicate 64 digit)] @?= Stem Leaf
   3154     call "bundleHashC" [hash, ofNumber 7] @?= ok hash (ofNumber 7)
   3155     forM_ invalid $ \bad ->
   3156       call "bundleHashC" [ofString bad, ofNumber 7] @?= err diagnostic (ofNumber 7)
   3157     -- Arbitrary malformed trees are rejected as well as canonical byte lists.
   3158     forM_ [Stem Leaf, Fork (Stem Leaf) Leaf,
   3159            foldr Fork (Stem Leaf) (replicate 64 (ofNumber 97)),
   3160            Fork (Fork (Fork Leaf Leaf) Leaf) (ofString (tail valid))] $ \bad ->
   3161       call "bundleHashValid?" [bad] @?= Leaf
   3162     -- A 65th cons is sufficient evidence: neither its byte nor tail is forced.
   3163     let oversized = foldr Fork
   3164           (Fork (error "65th byte inspected") (error "oversized suffix inspected"))
   3165           (replicate 64 (ofNumber 97))
   3166     call "bundleHashValid?" [oversized] @?= Leaf
   3167     let malformedByte = foldr Fork
   3168           (Fork (error "extra byte bit inspected") (error "extra byte suffix inspected"))
   3169           [Stem Leaf, Leaf, Leaf, Leaf, Leaf, Stem Leaf, Stem Leaf]
   3170     call "bundleHexByte?" [malformedByte] @?= Leaf
   3171     let missing = err (ofString "missing") Leaf
   3172         present = ok (ofString "bundle") Leaf
   3173         get value = call "getBundleByHash" [root, value]
   3174         serve value = call "serveBundleHash" [root, value]
   3175         route value = call "getBundleRoute"
   3176           [root, ofString "GET", ofString ("/_arboricx/bundle/hash/" ++ value)]
   3177         router value = call "arboricxRouter"
   3178           [root, ofString "GET", ofString ("/_arboricx/bundle/hash/" ++ value), Leaf, Leaf]
   3179         rejected = http "badRequestResponse" [diagnostic]
   3180     forM_ invalid $ \bad -> do
   3181       runRegistryEffects present (get (ofString bad)) >>= (@?= ([], err (ofNumber 400) diagnostic))
   3182       forM_ [serve (ofString bad), route bad, router bad] $ \action ->
   3183         runRegistryEffects present action >>= (@?= ([], rejected))
   3184     runRegistryEffects present (get hash) >>= (@?= ([expectedPath], present))
   3185     runRegistryEffects missing (get hash) >>=
   3186       (@?= ([expectedPath], err (ofNumber 404) (ofString "missing")))
   3187     let response = ofString $ "HTTP/1.1 200 OK\r\n" ++
   3188           "Content-Type: application/vnd.arboricx.bundle\r\n" ++
   3189           "Content-Length: 6\r\nConnection: close\r\n\r\nbundle"
   3190     forM_ [serve hash, route valid, router valid] $ \action -> do
   3191       runRegistryEffects present action >>= (@?= ([expectedPath], response))
   3192       runRegistryEffects missing action >>=
   3193         (@?= ([expectedPath], http "errorResponse" [ofNumber 404, ofString "missing"]))
   3194     forM_ [("/_arboricx/health", http "okResponse" [ofString "OK\n"]),
   3195            ("/", http "htmlResponse" [env Map.! "htmlLandingPage"])] $ \(target, expected) ->
   3196       runRegistryEffects present
   3197         (call "arboricxRouter" [root, ofString "GET", ofString target, Leaf, Leaf]) >>=
   3198         (@?= ([], expected))
   3199 
   3200 registryPipelineTests :: TestTree
   3201 registryPipelineTests = testGroup "Registry publication and startup"
   3202   [ testCase "complete effects: atomic upload, traversal rejection, init and socket failures" $ do
   3203       env <- evaluateCompilerFile "./lib/arboricx/server.tri"
   3204       let call name = foldl apply (env Map.! name)
   3205           root = ofString "/registry"
   3206           bytes = ofBytes (BS.pack [0,255,1])
   3207           hash = "47ffa3ea45a70b8a41c2c0825df323c00a8b7a01c1ea06083cc41dddcc001123"
   3208           primitive tag payload = Fork (ofNumber tag) payload
   3209           ok = parserOk Leaf Leaf
   3210           failed = parserErr (ofString "denied") (ofNumber 9)
   3211           digest = primitive 28 bytes
   3212           mkdir = primitive 25 (ofString "/registry/objects/47f")
   3213           publish = primitive 33 (Fork (ofString ("/registry/objects/47f/" ++ hash ++ ".arboricx")) bytes)
   3214           put = call "putBundle" [root, bytes]
   3215           prefix message = parserErr (ofString message) Leaf
   3216       forM_ ["putBundleWithHash", "putBundleWrite", "bundleTmpPath"] $ \name ->
   3217         assertBool ("unchecked/obsolete writer still exported: " ++ name) (Map.notMember name env)
   3218       runScriptedEffects put [(digest, parserOk (ofString hash) Leaf), (mkdir, ok), (publish, ok)]
   3219         >>= (@?= parserOk (ofString hash) Leaf)
   3220       runScriptedEffects put [(digest, failed)] >>= (@?= prefix "sha256Hex: denied")
   3221       runScriptedEffects put [(digest, parserOk (ofString hash) Leaf), (mkdir, failed)]
   3222         >>= (@?= prefix "createDirectory: denied")
   3223       runScriptedEffects put [(digest, parserOk (ofString hash) Leaf), (mkdir, ok), (publish, failed)]
   3224         >>= (@?= prefix "atomicWriteBytes: denied")
   3225       forM_ [ofString "../../../secret", ofString "/etc/passwd", ofString (replicate 65 'a'), Stem Leaf] $ \bad ->
   3226         runScriptedEffects put [(digest, parserOk bad Leaf)] >>=
   3227           (@?= prefix "sha256Hex: expected 64 lowercase hexadecimal hash characters")
   3228       runScriptedEffects (call "ensureStore" [root]) [(primitive 25 root, failed)]
   3229         >>= (@?= prefix "createDirectory: denied")
   3230       let sock = ofNumber 7
   3231           create = primitive 70 Leaf
   3232           bindSocket = primitive 72 (Fork sock (Fork (ofString "127.0.0.1") (ofNumber 9050)))
   3233           listen = primitive 73 (Fork sock (ofNumber 128))
   3234           close = primitive 71 sock
   3235           server = call "arboricxServer" [root, ofString "127.0.0.1", ofNumber 9050]
   3236       runScriptedEffects server [(create, failed)] >>= (@?= prefix "denied")
   3237       forM_ [ok, failed] $ \closed -> do
   3238         runScriptedEffects server [(create, parserOk sock Leaf), (bindSocket, failed), (close, closed)]
   3239           >>= (@?= prefix "denied")
   3240         runScriptedEffects server [(create, parserOk sock Leaf), (bindSocket, ok), (listen, failed), (close, closed)]
   3241           >>= (@?= prefix "denied")
   3242   , testCase "filesystem: initialization, binary publication, replacement and concurrent uploads" $
   3243       withSystemTempDirectory "registry-publication" $ \dir -> do
   3244         env <- evaluateCompilerFile "./lib/arboricx/server.tri"
   3245         let call name = foldl apply (env Map.! name)
   3246             rootPath = dir </> "store"
   3247             root = ofString rootPath
   3248             payload = BS.pack [0,255,1]
   3249             hash = "47ffa3ea45a70b8a41c2c0825df323c00a8b7a01c1ea06083cc41dddcc001123"
   3250             shard = rootPath </> "objects" </> "47f"
   3251             path = shard </> (hash ++ ".arboricx")
   3252             perms = defaultPerms { allowRead = [dir], allowWrite = [dir] }
   3253             run action = runIO perms (Fork (ofString "tricuIO") (Fork (ofNumber 1) action))
   3254             upload = run (call "putBundle" [root, ofBytes payload])
   3255             expected = Right (parserOk (ofString hash) Leaf)
   3256         run (call "ensureStore" [root]) >>= (@?= Right (parserOk Leaf Leaf))
   3257         run (call "ensureStore" [root]) >>= (@?= Right (parserOk Leaf Leaf))
   3258         upload >>= (@?= expected)
   3259         BS.readFile path >>= (@?= payload)
   3260         run (call "getBundleByHash" [root, ofString hash]) >>= (@?= Right (parserOk (ofBytes payload) Leaf))
   3261         -- Replacement is deliberate, not write-if-absent/corrupt-winner policy.
   3262         BS.writeFile path (BS.pack [7])
   3263         upload >>= (@?= expected)
   3264         BS.readFile path >>= (@?= payload)
   3265         outputs <- forM [1,2 :: Int] $ \_ -> do
   3266           output <- newEmptyMVar
   3267           _ <- forkIO ((try upload :: IO (Either SomeException (Either String T))) >>= putMVar output)
   3268           pure output
   3269         completed <- Timeout.timeout 10000000 (mapM takeMVar outputs)
   3270         case completed of
   3271           Nothing -> assertFailure "concurrent registry uploads timed out"
   3272           Just results -> forM_ results $ \result -> case result of
   3273             Left problem -> assertFailure (show problem)
   3274             Right value -> value @?= expected
   3275         BS.readFile path >>= (@?= payload)
   3276         listDirectory shard >>= (@?= [hash ++ ".arboricx"])
   3277         listDirectory (rootPath </> "tmp") >>= (@?= [])
   3278         -- An existing file cannot masquerade as an initialized directory.
   3279         let blocked = dir </> "blocked"
   3280         BS.writeFile blocked BS.empty
   3281         result <- run (call "ensureStore" [ofString blocked])
   3282         result @?= Right (parserErr (ofString "createDirectory: already exists") Leaf)
   3283   ]
   3284 
   3285 -- Tiny source probes run through the authenticated portable compiler fixture.
   3286 -- Force complete generated trees before applying deliberately partial host data.
   3287 localControlTests :: TestTree
   3288 localControlTests = testGroup "Local control construction and non-strictness"
   3289   [ testCase "case constructs code, skips unselected work and preserves wildcard laziness" $ do
   3290       selected <- compileLocalControl [] $ unlines
   3291         [ "main input = case input of"
   3292         , "  t = 23"
   3293         , "  _ = let rec loop x = loop x in loop t" ]
   3294       apply selected Leaf @?= ofNumber 23
   3295       ignored <- compileLocalControl [] "main input = case input of\n  _ = 23"
   3296       apply ignored (error "wildcard forced its subject") @?= ofNumber 23
   3297       first <- compileLocalControl [] "main input = case input of\n  (head, _) = head\n  _ = t"
   3298       apply first (Fork (ofNumber 23) (error "case forced an ignored sibling")) @?= ofNumber 23
   3299   , testCase "local recursive hash worker rejects poisoned byte/tail with bounded inspection" $ do
   3300       server <- evaluateCompilerFile "./lib/arboricx/server.tri"
   3301       math <- evaluateCompilerFile "./lib/math/native.tri"
   3302       function <- compileLocalControl
   3303         [("true", Stem Leaf), ("false", Leaf),
   3304          ("hex", server Map.! "bundleHexByte?"), ("subtract", math Map.! "subtract")] $ unlines
   3305         [ "main hash = (let rec"
   3306         , "  walk [] 0 = true"
   3307         , "  walk _ 0 = false"
   3308         , "  walk [byte | rest] remaining = case (hex byte) of"
   3309         , "    ^true = walk rest (subtract remaining 1)"
   3310         , "    _ = false"
   3311         , "  walk _ _ = false"
   3312         , "  in walk hash 64)" ]
   3313       apply function (ofString (replicate 64 'a')) @?= Stem Leaf
   3314       let oversized = foldr Fork
   3315             (Fork (error "local worker inspected byte 65") (error "local worker inspected suffix"))
   3316             (replicate 64 (ofNumber 97))
   3317           malformedByte = foldr Fork
   3318             (Fork (error "local worker inspected extra bit") (error "local worker inspected numeral suffix"))
   3319             [Stem Leaf, Leaf, Leaf, Leaf, Leaf, Stem Leaf, Stem Leaf]
   3320       apply function oversized @?= Leaf
   3321       apply function (Fork malformedByte (error "local worker continued after malformed byte")) @?= Leaf
   3322   ]
   3323 
   3324 compileLocalControl :: [(String, T)] -> String -> IO T
   3325 compileLocalControl bindings source = do
   3326   unit <- evaluateCompilerFile "./lib/source/unit.tri"
   3327   let external = ofList [Fork (ofString name) value | (name,value) <- bindings]
   3328       answer = apply (apply (unit Map.! "compileStableModuleDefinitions") external) (ofString source)
   3329       forceTree Leaf = ()
   3330       forceTree (Stem child) = forceTree child
   3331       forceTree (Fork left right) = forceTree left `seq` forceTree right
   3332   case answer of
   3333     Fork (Stem Leaf) (Fork (Fork (Fork name function) Leaf) Leaf) -> do
   3334       name @?= ofString "main"
   3335       evaluate (forceTree function)
   3336       pure function
   3337     other -> assertFailure ("local control compilation failed: " ++ show other) >> pure Leaf
   3338 
   3339 httpPipelineTests :: TestTree
   3340 httpPipelineTests = testCase "HTTP reference pipeline: buffers, EOF, receive errors and cleanup" $ do
   3341   let env = fixtureExports "http"
   3342       call name = foldl apply (env Map.! name)
   3343       sock = ofNumber 7
   3344       recv size = pairT (ofNumber 76) (pairT sock (ofNumber size))
   3345       send bytes = pairT (ofNumber 77) (pairT sock (ofString bytes))
   3346       close = pairT (ofNumber 71) sock
   3347       ok = parserOk Leaf Leaf
   3348       failed = parserErr (ofString "broken") Leaf
   3349       bad message = parserErr (ofNumber 400) (ofString message)
   3350   call "statusPhrase" [ofNumber 413] @?= ofString "Content Too Large"
   3351   forM_ [(0, "abc", "", "abc"), (2, "abc", "ab", "c")] $ \(count, input, body, rest) ->
   3352     runScriptedEffects (call "readBodyExact" [sock, ofNumber count, ofString input]) [] >>=
   3353       (@?= parserOk (ofString body) (ofString rest))
   3354   runScriptedEffects (call "readBodyExact" [sock, ofNumber 3, ofString "a"])
   3355     [(recv 1, parserOk (ofString "b") Leaf), (recv 1, parserOk (ofString "c") Leaf)] >>=
   3356       (@?= parserOk (ofString "abc") Leaf)
   3357   -- Exact-length invariant, including short reads and oversized initial buffers.
   3358   forM_ [0..8 :: Int] $ \count ->
   3359     forM_ [0..count+2] $ \initialSize -> do
   3360       let bytes = "AbCdEfGhIjK"
   3361           initial = take initialSize bytes
   3362           chunks = take (count - initialSize) (drop initialSize bytes)
   3363           script = [(recv 1, parserOk (ofString [byte]) Leaf) | byte <- chunks]
   3364       runScriptedEffects (call "readBodyExact" [sock, ofNumber (fromIntegral count), ofString initial]) script >>=
   3365         (@?= parserOk (ofString (take count bytes)) (ofString (drop count initial)))
   3366   runScriptedEffects (call "readBodyExact" [sock, ofNumber 3, ofString "a"])
   3367     [(recv 1, parserOk Leaf Leaf)] >>= (@?= bad "Unexpected end of request body\n")
   3368   runScriptedEffects (call "readBodyExact" [sock, ofNumber 3, Leaf])
   3369     [(recv 1, failed)] >>= (@?= bad "recv failed while reading body: broken")
   3370   runScriptedEffects (call "recvUntilMax" [sock, ofString "\r\n\r\n", ofNumber 20])
   3371     [(recv 4096, parserOk (ofString "abc") Leaf), (recv 4096, failed)] >>=
   3372       (@?= parserErr (ofNumber 400) (ofString "abc"))
   3373   let message = "Request Header Fields Too Large\n"
   3374       response431 = "HTTP/1.1 431 Request Header Fields Too Large\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: " ++
   3375         show (length message) ++ "\r\nConnection: close\r\n\r\n" ++ message
   3376   runScriptedEffects (call "httpHandleReceived" [Leaf, sock, parserErr (ofNumber 431) (ofString "raw")])
   3377     [(send response431, ok), (close, ok)] >>= (@?= ok)
   3378   forM_ [ok, failed, parserErr (ofString "broken") (ofString "context")] $ \sent ->
   3379     forM_ [ok, failed] $ \closed ->
   3380       runScriptedEffects (call "respondAndClose" [sock, ofString "response"])
   3381         [(send "response", sent), (close, closed)] >>= (@?= sent)
   3382   -- Host-side negative controls for missing, wrong and leftover effects.
   3383   forM_ [(recv 1, []), (recv 1, [(close, ok)]), (pairT (ofNumber 0) Leaf, [(close, ok)])] $ \(action, script) -> do
   3384     observed <- try (runScriptedEffects action script) :: IO (Either SomeException T)
   3385     case observed of
   3386       Left _ -> pure ()
   3387       Right _ -> assertFailure "HTTP effect interpreter accepted a mismatched script"
   3388 
   3389 containsAll :: String -> [String] -> Assertion
   3390 containsAll text needles =
   3391   forM_ needles $ \needle ->
   3392     assertBool ("expected " ++ show needle ++ " in: " ++ text) (needle `isInfixOf` text)
   3393 
   3394 
   3395 ioOkResult :: T -> T
   3396 ioOkResult val = Fork (Stem Leaf) (Fork val Leaf)
   3397 
   3398 ioErrResult :: String -> T
   3399 ioErrResult msg = Fork Leaf (Fork (ofString msg) Leaf)