packages feed

canontra-0.1.0.0: test/Canontra/MetamorphicSpec.hs

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Canontra.MetamorphicSpec
Description : Automated metamorphic mutation testing suite for v0.0.9-alpha.

Validates the formal Soundness Invariance Theorem and Sensitivity Divergence
Theorem across polyglot programs (Python, JavaScript, TypeScript, Go, Rust),
proving algebraic invariance under semantics-preserving transformations and
strict divergence under semantic mutations.
-}
module Canontra.MetamorphicSpec (spec) where

import qualified Data.Text as T
import Test.Hspec
import Test.QuickCheck

import Canontra.Fingerprint.Bundle (computeBundleFromSource)
import Canontra.Fingerprint.Composite (computeF4)
import Canontra.Fingerprint.Declaration (computeF2)
import Canontra.Fingerprint.Structural (computeF1)
import Canontra.Fingerprint.TypeContract (computeFT)
import Canontra.IR.Declaration
import Canontra.IR.Expression
import Canontra.IR.Program
import Canontra.Security.Path (canonicalizeSafePath, checkResourceBounds)
import Canontra.Types
import Canontra.Verification.Metamorphic

spec :: Spec
spec = do
  describe "Canontra.Verification.Metamorphic" $ do

    -- =========================================================================
    -- 1. Soundness Invariance Theorems (T in T_sound)
    -- =========================================================================
    describe "Soundness Invariance Theorems (T in T_sound)" $ do

      it "Python: preserves all semantic tiers F1..F4 under whitespace and blank line jitter" $ do
        let code = T.unlines
              [ "def calculate_tax(subtotal: float, rate: float = 0.05) -> float:"
              , "    tax = subtotal * rate"
              , "    return subtotal + tax"
              ]
        case verifyMetamorphicSourceTransform "calc.py" code (ReformatWhitespaceTrivia 4) of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            mvF0Different verdict `shouldBe` True
            mvF1Identical verdict `shouldBe` True
            mvF2Identical verdict `shouldBe` True
            mvF3Identical verdict `shouldBe` True
            mvFCGIdentical verdict `shouldBe` True
            mvFCFIdentical verdict `shouldBe` True
            mvFDFIdentical verdict `shouldBe` True
            mvF4Identical verdict `shouldBe` True
            mvSoundnessPassed verdict `shouldBe` True

      it "TypeScript: preserves F1..F4 under arbitrary whitespace and newline padding" $ do
        let code = T.unlines
              [ "function computeArea(width: number, height: number): number {"
              , "    const area = width * height;"
              , "    return area;"
              , "}"
              ]
        case verifyMetamorphicSourceTransform "area.ts" code (ReformatWhitespaceTrivia 3) of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            mvF1Identical verdict `shouldBe` True
            mvF2Identical verdict `shouldBe` True
            mvF4Identical verdict `shouldBe` True
            mvSoundnessPassed verdict `shouldBe` True

      it "Go: preserves F1..F4 under indentation and whitespace jitter" $ do
        let code = T.unlines
              [ "package main"
              , "func Max(a int, b int) int {"
              , "    if a > b {"
              , "        return a"
              , "    }"
              , "    return b"
              , "}"
              ]
        case verifyMetamorphicSourceTransform "math.go" code (ReformatWhitespaceTrivia 2) of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            mvF1Identical verdict `shouldBe` True
            mvF2Identical verdict `shouldBe` True
            mvF4Identical verdict `shouldBe` True
            mvSoundnessPassed verdict `shouldBe` True

      it "Rust: preserves F1..F4 under whitespace and brace formatting variation" $ do
        let code = T.unlines
              [ "fn add_two(x: i32, y: i32) -> i32 {"
              , "    let sum = x + y;"
              , "    return sum;"
              , "}"
              ]
        case verifyMetamorphicSourceTransform "add.rs" code (ReformatWhitespaceTrivia 4) of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            mvF1Identical verdict `shouldBe` True
            mvF2Identical verdict `shouldBe` True
            mvF4Identical verdict `shouldBe` True
            mvSoundnessPassed verdict `shouldBe` True

      it "Python: strips unflagged docstrings and comments preserving F1..F4" $ do
        let code = T.unlines
              [ "def process_data(items: list) -> int:"
              , "    # Initial accumulator"
              , "    total = 0"
              , "    for x in items:"
              , "        total = total + x"
              , "    return total"
              ]
        case verifyMetamorphicSourceTransform "proc.py" code (InsertInlineDocstrings "Temporary developer comment") of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            mvF0Different verdict `shouldBe` True
            mvF1Identical verdict `shouldBe` True
            mvF4Identical verdict `shouldBe` True
            mvSoundnessPassed verdict `shouldBe` True

      it "AST: canonically sorts independent pure functions yielding 100% bit-identical F1..F4" $ do
        let fnA = DeclFunction (Function "alpha" [] Nothing [] [StmtReturn (Just (ExprLit (LitInt 1)))] False)
            fnB = DeclFunction (Function "beta" [] Nothing [] [StmtReturn (Just (ExprLit (LitInt 2)))] False)
            fnC = DeclFunction (Function "gamma" [] Nothing [] [StmtReturn (Just (ExprLit (LitInt 3)))] False)
            prog = Program [Module "main" [] [fnA, fnB, fnC] []] "python"
            verdict = verifyMetamorphicProgramTransform prog ReorderPureDeclarations
        mvF1Identical verdict `shouldBe` True
        mvF2Identical verdict `shouldBe` True
        mvF4Identical verdict `shouldBe` True
        mvSoundnessPassed verdict `shouldBe` True

      it "AST: eliminates StmtPass dead statements yielding 100% bit-identical F1..F4" $ do
        let fn = DeclFunction (Function "calc" [] Nothing [] [StmtReturn (Just (ExprLit (LitInt 42)))] False)
            prog = Program [Module "main" [] [fn] []] "python"
            verdict = verifyMetamorphicProgramTransform prog InsertDeadStatement
        mvF1Identical verdict `shouldBe` True
        mvF2Identical verdict `shouldBe` True
        mvF4Identical verdict `shouldBe` True
        mvSoundnessPassed verdict `shouldBe` True

      it "AST: local variable alpha-renaming preserves public signature F2 and dependencies F3" $ do
        let fn = DeclFunction (Function "run" [] Nothing []
                  [ StmtAssign [ExprId "local_var"] (ExprLit (LitInt 10))
                  , StmtReturn (Just (ExprId "local_var"))
                  ] False)
            prog = Program [Module "main" [] [fn] []] "python"
            verdict = verifyMetamorphicProgramTransform prog (AlphaRenameLocalVar "local_var" "renamed_var")
        mvF2Identical verdict `shouldBe` True
        mvF3Identical verdict `shouldBe` True
        mvFCGIdentical verdict `shouldBe` True

      it "AST: inverted branch condition with swapped arms preserves semantic structure" $ do
        let ifStmt = StmtIf (ExprId "flag")
                            [StmtReturn (Just (ExprLit (LitInt 1)))]
                            [StmtReturn (Just (ExprLit (LitInt 2)))]
            fn = DeclFunction (Function "decide" [] Nothing [] [ifStmt] False)
            prog = Program [Module "main" [] [fn] []] "python"
            trans = applyAstTransform InvertBranchCondition prog
        progLanguage trans `shouldBe` "python"

    -- =========================================================================
    -- 2. Sensitivity Divergence Theorems (M in M_divergent)
    -- =========================================================================
    describe "Sensitivity Divergence Theorems (M in M_divergent)" $ do

      it "Arithmetic operator mutation (+ -> -) causes strict F1 and F4 divergence" $ do
        let code = T.unlines
              [ "def add_vals(a: int, b: int) -> int:"
              , "    return a + b"
              ]
        case verifySourceMutation "add.py" code (MutFlipArithmeticOp OpAdd OpSub) of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            msvF1Diverged verdict `shouldBe` True
            msvF4Diverged verdict `shouldBe` True
            msvSensitivityPassed verdict `shouldBe` True

      it "Comparison operator mutation (< -> >) causes strict F1 and F4 divergence" $ do
        let code = T.unlines
              [ "def is_less(a: int, b: int) -> bool:"
              , "    if a < b:"
              , "        return True"
              , "    return False"
              ]
        case verifySourceMutation "comp.py" code (MutFlipComparisonOp OpLt OpGt) of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            msvF1Diverged verdict `shouldBe` True
            msvF4Diverged verdict `shouldBe` True
            msvSensitivityPassed verdict `shouldBe` True

      it "Equality operator mutation (== -> !=) causes strict F1 and F4 divergence" $ do
        let code = T.unlines
              [ "def check_equal(x: int, y: int) -> bool:"
              , "    return x == y"
              ]
        case verifySourceMutation "eq.py" code (MutFlipComparisonOp OpEq OpNotEq) of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            msvF1Diverged verdict `shouldBe` True
            msvF4Diverged verdict `shouldBe` True
            msvSensitivityPassed verdict `shouldBe` True

      it "Numeric literal mutation (0 -> 9999) causes strict F1 and F4 divergence" $ do
        let code = T.unlines
              [ "def get_baseline() -> int:"
              , "    base = 0"
              , "    return base"
              ]
        case verifySourceMutation "lit.py" code (MutAlterNumericLit 0 9999) of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            msvF1Diverged verdict `shouldBe` True
            msvF4Diverged verdict `shouldBe` True
            msvSensitivityPassed verdict `shouldBe` True

      it "String literal mutation causes strict F1 and F4 divergence" $ do
        let code = T.unlines
              [ "def greet() -> str:"
              , "    return \"hello\""
              ]
        case verifySourceMutation "greet.py" code (MutAlterStringLit "hello" "goodbye") of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            msvF1Diverged verdict `shouldBe` True
            msvF4Diverged verdict `shouldBe` True
            msvSensitivityPassed verdict `shouldBe` True

      it "Branch condition inversion without arm swap causes strict divergence" $ do
        let code = T.unlines
              [ "def authenticate(valid: bool) -> int:"
              , "    if valid:"
              , "        return 1"
              , "    return 0"
              ]
        case verifySourceMutation "auth.py" code MutInvertConditionOnly of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            msvF1Diverged verdict `shouldBe` True
            msvF4Diverged verdict `shouldBe` True
            msvSensitivityPassed verdict `shouldBe` True

      it "Dropping an execution statement causes strict F1 and F4 divergence" $ do
        let code = T.unlines
              [ "def calculate() -> int:"
              , "    x = 10"
              , "    return x"
              ]
        case verifySourceMutation "calc.py" code MutDropExecutionStmt of
          Left err -> expectationFailure (show err)
          Right verdict -> do
            msvF1Diverged verdict `shouldBe` True
            msvF4Diverged verdict `shouldBe` True
            msvSensitivityPassed verdict `shouldBe` True

      it "Public declaration parameter mutation causes strict F2 and F4 divergence" $ do
        let fn = DeclFunction (Function "query" [Parameter "old_param" ParamPositional Nothing Nothing] Nothing [] [] False)
            prog = Program [Module "main" [] [fn] []] "python"
            verdict = verifyProgramMutation prog (MutAlterSignatureParam "old_param" "new_param")
        msvSensitivityPassed verdict `shouldBe` True

    -- =========================================================================
    -- 3. Polyglot Multi-Language Metamorphic Corpus
    -- =========================================================================
    describe "Polyglot Multi-Language Metamorphic Corpus" $ do

      it "evaluates multi-language metamorphic suite with 100% passing soundness and sensitivity" $ do
        let fixtures =
              [ ( "sample.py"
                , T.unlines
                    [ "def sum_two(a: int, b: int) -> int:"
                    , "    return a + b"
                    ]
                )
              , ( "sample.ts"
                , T.unlines
                    [ "function multiply(x: number, y: number): number {"
                    , "    return x * y;"
                    , "}"
                    ]
                )
              , ( "sample.go"
                , T.unlines
                    [ "package main"
                    , "func Sub(a int, b int) int {"
                    , "    return a - b"
                    , "}"
                    ]
                )
              ]
            summary = runMetamorphicSuite fixtures
        mssTotalCases summary `shouldSatisfy` (> 0)
        mssSoundnessPassed summary `shouldSatisfy` (> 0)
        mssSensitivityPassed summary `shouldSatisfy` (> 0)
        mssAllPassed summary `shouldBe` True

      it "formats a formal ASCII Metamorphic Verification Report" $ do
        let summary = MetamorphicSuiteSummary 20 12 8 True
            report = formatMetamorphicSummary summary
        T.isInfixOf "CANONTRA METAMORPHIC MUTATION VERIFICATION REPORT" report `shouldBe` True
        T.isInfixOf "100% METAMORPHICALLY SOUND" report `shouldBe` True

    -- =========================================================================
    -- 4. Property-Based Metamorphic QuickCheck Invariants
    -- =========================================================================
    describe "Property-Based Metamorphic QuickCheck Invariants" $ do

      it "Property: arbitrary whitespace padding preserves F1 and F4 soundness" $ do
        property $ \(Positive n) ->
          let pad = n `mod` 16
              code = "def f(x: int) -> int:\n    return x * 2\n"
          in case verifyMetamorphicSourceTransform "p.py" code (ReformatWhitespaceTrivia pad) of
               Left _ -> False
               Right v -> mvSoundnessPassed v

      it "Property: arbitrary integer constant mutations cause strict divergence" $ do
        property $ \(n1, n2) ->
          (n1 /= n2 && n1 >= 0 && n2 >= 0 && n1 < 100 && n2 < 100) ==>
            let code = "def val():\n    return " <> T.pack (show (n1 :: Integer)) <> "\n"
            in case verifySourceMutation "lit.py" code (MutAlterNumericLit n1 n2) of
                 Left _ -> False
                 Right v -> msvSensitivityPassed v

      it "Property: arbitrary comment text injection preserves F1 and F4 soundness" $ do
        property $ \(NonEmpty s) ->
          let safeComment = T.filter (\c -> c >= 'a' && c <= 'z') (T.pack s)
              code = "def proc(x: int) -> int:\n    y = x + 1\n    return y\n"
          in not (T.null safeComment) ==>
               case verifyMetamorphicSourceTransform "comm.py" code (InsertInlineDocstrings safeComment) of
                 Left _ -> False
                 Right v -> mvSoundnessPassed v

    -- =========================================================================
    -- 5. Extended Multi-Tier Metamorphic Invariance Invariants
    -- =========================================================================
    describe "Extended Multi-Tier Metamorphic Invariance Invariants" $ do

      it "Python: preserves F1..F4 across multi-line bracketed expressions with trailing commas" $ do
        let code1 = T.unlines
              [ "def get_items():"
              , "    return [1, 2, 3]"
              ]
        let code2 = T.unlines
              [ "def get_items():"
              , "    return ["
              , "        1,"
              , "        2,"
              , "        3,"
              , "    ]"
              ]
        case (computeBundleFromSource "i1.py" code1, computeBundleFromSource "i2.py" code2) of
          (Right b1, Right b2) -> do
            f0Source b1 `shouldNotBe` f0Source b2
            f1Structural b1 `shouldBe` f1Structural b2
            f4Composite b1 `shouldBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Python: preserves F1..F4 across consecutive empty lines and redundant comment lines" $ do
        let code1 = "def fn():\n    return 42\n"
        let code2 = "\n\n# Header\n# Comment\n\ndef fn():\n    # Inside\n    return 42\n\n\n"
        case (computeBundleFromSource "f1.py" code1, computeBundleFromSource "f2.py" code2) of
          (Right b1, Right b2) -> do
            f1Structural b1 `shouldBe` f1Structural b2
            f2Declaration b1 `shouldBe` f2Declaration b2
            f4Composite b1 `shouldBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "TypeScript: preserves F1..F4 when function parameters have identical types but varying whitespace" $ do
        let code1 = "function add(a: number, b: number): number { return a + b; }"
        let code2 = "function add( a : number , b : number ) : number {\n    return a + b;\n}"
        case (computeBundleFromSource "add1.ts" code1, computeBundleFromSource "add2.ts" code2) of
          (Right b1, Right b2) -> do
            f1Structural b1 `shouldBe` f1Structural b2
            f2Declaration b1 `shouldBe` f2Declaration b2
            f4Composite b1 `shouldBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Go: preserves F1..F4 when functions have trailing newlines or block comments" $ do
        let code1 = "package main\nfunc Run() int {\n    return 10\n}\n"
        let code2 = "package main\n/* block comment */\nfunc Run() int {\n    // line comment\n    return 10\n}\n\n"
        case (computeBundleFromSource "r1.go" code1, computeBundleFromSource "r2.go" code2) of
          (Right b1, Right b2) -> do
            f1Structural b1 `shouldBe` f1Structural b2
            f4Composite b1 `shouldBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Rust: preserves F1..F4 across formatting variations of let bindings" $ do
        let code1 = "fn test() -> i32 { let x = 5; return x; }"
        let code2 = "fn test() -> i32 {\n    let x = 5;\n    return x;\n}\n"
        case (computeBundleFromSource "t1.rs" code1, computeBundleFromSource "t2.rs" code2) of
          (Right b1, Right b2) -> do
            f1Structural b1 `shouldBe` f1Structural b2
            f4Composite b1 `shouldBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "AST: preserves F2 and F3 when local variable names are alpha-renamed across multiple scopes" $ do
        let fn1 = DeclFunction (Function "foo" [] Nothing []
                    [ StmtAssign [ExprId "temp_a"] (ExprLit (LitInt 1))
                    , StmtReturn (Just (ExprId "temp_a"))
                    ] False)
            fn2 = DeclFunction (Function "bar" [] Nothing []
                    [ StmtAssign [ExprId "temp_b"] (ExprLit (LitInt 2))
                    , StmtReturn (Just (ExprId "temp_b"))
                    ] False)
            prog = Program [Module "m" [] [fn1, fn2] []] "python"
            trans1 = applyAstTransform (AlphaRenameLocalVar "temp_a" "var_alpha") prog
            trans2 = applyAstTransform (AlphaRenameLocalVar "temp_b" "var_beta") trans1
            bOrig = computeF2 prog
            bTrans = computeF2 trans2
        bOrig `shouldBe` bTrans

      it "AST: permuting 4 pure functions produces 100% bit-identical F1, F2, and F4" $ do
        let fns = [ DeclFunction (Function ("fn_" <> T.pack (show i)) [] Nothing [] [StmtReturn (Just (ExprLit (LitInt i)))] False)
                  | i <- [1..4 :: Integer]
                  ]
            progA = Program [Module "main" [] fns []] "python"
            progB = Program [Module "main" [] (reverse fns) []] "python"
        computeF1 progA `shouldBe` computeF1 progB
        computeF2 progA `shouldBe` computeF2 progB
        computeF4 (computeF1 progA) (computeF2 progA) (Fingerprint "f3") (Fingerprint "fcg") (Fingerprint "fcf") (Fingerprint "fdf") (computeFT progA)
          `shouldBe`
          computeF4 (computeF1 progB) (computeF2 progB) (Fingerprint "f3") (Fingerprint "fcg") (Fingerprint "fcf") (Fingerprint "fdf") (computeFT progB)

      it "Sensitivity: mutating float literals causes strict F1 and F4 divergence" $ do
        let code1 = "def get_pi():\n    return 3.14159\n"
        let code2 = "def get_pi():\n    return 2.71828\n"
        case (computeBundleFromSource "pi1.py" code1, computeBundleFromSource "pi2.py" code2) of
          (Right b1, Right b2) -> do
            f1Structural b1 `shouldNotBe` f1Structural b2
            f4Composite b1 `shouldNotBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Sensitivity: inverting return boolean literal (True -> False) causes strict divergence" $ do
        let code1 = "def is_active():\n    return True\n"
        let code2 = "def is_active():\n    return False\n"
        case (computeBundleFromSource "b1.py" code1, computeBundleFromSource "b2.py" code2) of
          (Right b1, Right b2) -> do
            f1Structural b1 `shouldNotBe` f1Structural b2
            f4Composite b1 `shouldNotBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Sensitivity: mutating default parameter value strictly alters F2 declaration signature" $ do
        let code1 = "def connect(port: int = 8080):\n    return port\n"
        let code2 = "def connect(port: int = 9090):\n    return port\n"
        case (computeBundleFromSource "c1.py" code1, computeBundleFromSource "c2.py" code2) of
          (Right b1, Right b2) -> do
            f2Declaration b1 `shouldNotBe` f2Declaration b2
            f4Composite b1 `shouldNotBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Sensitivity: changing arithmetic operator from multiplication to division alters F1" $ do
        let code1 = "def scale(x: int): return x * 10\n"
        let code2 = "def scale(x: int): return x / 10\n"
        case (computeBundleFromSource "s1.py" code1, computeBundleFromSource "s2.py" code2) of
          (Right b1, Right b2) -> do
            f1Structural b1 `shouldNotBe` f1Structural b2
            f4Composite b1 `shouldNotBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Sensitivity: adding an extra parameter alters declaration hash F2" $ do
        let code1 = "def query(term: str): return term\n"
        let code2 = "def query(term: str, limit: int = 10): return term\n"
        case (computeBundleFromSource "q1.py" code1, computeBundleFromSource "q2.py" code2) of
          (Right b1, Right b2) -> do
            f2Declaration b1 `shouldNotBe` f2Declaration b2
            f4Composite b1 `shouldNotBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Soundness: multiple consecutive whitespace jitter passes preserve invariant F1 and F4" $ do
        let code0 = "def compute(x: int) -> int:\n    return x * 2 + 1\n"
            code1 = applySourceTransform (ReformatWhitespaceTrivia 2) code0
            code2 = applySourceTransform (ReformatWhitespaceTrivia 6) code1
        case (computeBundleFromSource "p0.py" code0, computeBundleFromSource "p2.py" code2) of
          (Right b0, Right b2) -> do
            f1Structural b0 `shouldBe` f1Structural b2
            f4Composite b0 `shouldBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Sensitivity: changing return type annotation in TypeScript strictly alters F2 declaration hash" $ do
        let code1 = "function getVal(): string { return \"val\"; }\n"
            code2 = "function getVal(): number { return 123; }\n"
        case (computeBundleFromSource "v1.ts" code1, computeBundleFromSource "v2.ts" code2) of
          (Right b1, Right b2) -> do
            f2Declaration b1 `shouldNotBe` f2Declaration b2
            f4Composite b1 `shouldNotBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

      it "Sensitivity: flipping boolean literal True to False strictly alters F1 structural AST hash" $ do
        let code1 = "def is_active(): return True\n"
            code2 = "def is_active(): return False\n"
        case (computeBundleFromSource "b1.py" code1, computeBundleFromSource "b2.py" code2) of
          (Right b1, Right b2) -> do
            f1Structural b1 `shouldNotBe` f1Structural b2
            f4Composite b1 `shouldNotBe` f4Composite b2
          _ -> expectationFailure "Parse failed"

    -- =========================================================================
    -- 6. Phase 6 Production Metamorphic Expansion & Security Invariants
    -- =========================================================================
    describe "Phase 6 Production Verification & Boundary Invariants" $ do

      describe "Type Contract F_T Invariance across Languages" $ do
        it "TypeScript: interface method reordering produces bit-identical F_T" $ do
          let tsA = "export interface API {\n  get(k: string): string;\n  set(k: string, v: string): void;\n}\n"
              tsB = "export interface API {\n  set(k: string, v: string): void;\n  get(k: string): string;\n}\n"
          case (computeBundleFromSource "api1.ts" tsA, computeBundleFromSource "api2.ts" tsB) of
            (Right bA, Right bB) -> do
              fTTypeContract bA `shouldBe` fTTypeContract bB
              unFingerprint (fTTypeContract bA) `shouldNotBe` ""
            _ -> expectationFailure "TypeScript parse failed"

        it "Go: interface method reordering produces bit-identical F_T" $ do
          let goA = "package p\ntype DB interface {\n  Close() error\n  Ping() error\n}\n"
              goB = "package p\ntype DB interface {\n  Ping() error\n  Close() error\n}\n"
          case (computeBundleFromSource "db1.go" goA, computeBundleFromSource "db2.go" goB) of
            (Right bA, Right bB) -> do
              fTTypeContract bA `shouldBe` fTTypeContract bB
              unFingerprint (fTTypeContract bA) `shouldNotBe` ""
            _ -> expectationFailure "Go parse failed"

        it "Rust: trait method reordering produces bit-identical F_T" $ do
          let rsA = "trait Worker {\n  fn run(&self) -> bool;\n  fn stop(&self) -> bool;\n}\n"
              rsB = "trait Worker {\n  fn stop(&self) -> bool;\n  fn run(&self) -> bool;\n}\n"
          case (computeBundleFromSource "w1.rs" rsA, computeBundleFromSource "w2.rs" rsB) of
            (Right bA, Right bB) -> do
              fTTypeContract bA `shouldBe` fTTypeContract bB
              unFingerprint (fTTypeContract bA) `shouldNotBe` ""
            _ -> expectationFailure "Rust parse failed"

      describe "Air-Gapped Path Sandboxing & Security Boundaries" $ do
        it "rejects parent directory escape attempts" $ do
          res <- canonicalizeSafePath "src" "../../etc/passwd"
          case res of
            Left _  -> pure ()
            Right p -> expectationFailure ("Path escape was not caught: " ++ p)

        it "accepts legitimate nested relative paths within project root" $ do
          res <- canonicalizeSafePath "." "src/Canontra/Types.hs"
          case res of
            Left err -> expectationFailure ("Legitimate path rejected: " ++ err)
            Right _  -> pure ()

        it "enforces recursion depth boundary when nesting exceeds ceiling" $ do
          let deepPath = concat (replicate 70 "sub/") ++ "file.txt"
          res <- checkResourceBounds deepPath
          case res of
            Left _  -> pure ()
            Right _ -> expectationFailure "Excessive nesting depth was not rejected"