File size: 6,692 Bytes
4953a87 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | ||| Manifest.seal read/write and per-file gate verification.
|||
||| Copyright (C) 2026 SNAPKITTYWEST / SnapKitty (Jessica)
||| License: BSL-1.1 / AGPL-3.0 / MPL-2.0
module Manifest
import Data.String
import Data.List
import Data.Maybe
import System.File
import System.File.ReadWrite
import Hash
import WORM
-- ββ Registered modules βββββββββββββββββββββββββββββββββββββββββββββββββββββ
||| Every file the clone gate tracks.
||| All zeros = UNSEALED. Run `clone_gate seal` to populate.
export
registeredModules : List String
registeredModules =
[ "clone_gate/src/Hash.idr"
, "clone_gate/src/WORM.idr"
, "clone_gate/src/Manifest.idr"
, "clone_gate/src/Main.idr"
, "clone_gate/cbits/hash_shim.c"
, "crates/engine/src/recurrence.rs"
, "crates/engine/src/microbit.rs"
, "crates/engine/src/cad_kernel.rs"
, "crates/engine/src/icp_model.rs"
, "crates/engine/src/mmep_relaxation.rs"
, "crates/kani-verification/src/lib.rs"
, "crates/kani-verification/src/arithmetic_loop.rs"
, "lean/Multiplicity/Dynamics/Contraction.lean"
, "lean/Multiplicity/Dynamics/GTHZHarmony.lean"
, "lean/WalkAA/WalkAmplitudeAmplification.lean"
, "lean/WalkAA/WormholeWalk.lean"
, "agda/MultiplicityInvariants.agda"
, "agda/PrimitiveShattering.agda"
, "agda/GTHZHarmony.agda"
, "haskell/JohnsonWalk.hs"
, "haskell/JohnsonWalkStorable.hs"
, "haskell/JohnsonWalkParallel.hs"
, "haskell/WormholeWalkF2.hs"
, "circuits/MicrobitFullAdder.circom"
, "circuits/MicrobitAdderAndDrift.circom"
, "hardware/microbit_interlock.sv"
, "tools/f2_solver.py"
]
-- ββ MANIFEST.seal path βββββββββββββββββββββββββββββββββββββββββββββββββββββ
export
manifestPath : String
manifestPath = "MANIFEST.seal"
-- ββ Seal all registered modules ββββββββββββββββββββββββββββββββββββββββββββ
||| Hash every registered module and write MANIFEST.seal.
||| Returns the number of entries sealed.
export
sealAll : HasIO io => String -> io Nat
sealAll repoRoot = do
let go : (1 c : Chain n) -> List String -> io (n : Nat ** Chain n)
go c [] = pure (_ ** c)
go c (rel :: rest) = do
let abs = repoRoot ++ "/" ++ rel
let lbl = "module:" ++ rel
c' <- sealFile c lbl rel abs
go c' rest
(_ ** chain) <- go Empty registeredModules
let entries = toList chain
let jsonl = unlines (map entryToJsonl entries)
Right _ <- writeFile manifestPath jsonl
| Left err => do
putStrLn $ "[clone_gate] ERROR writing " ++ manifestPath ++ ": " ++ show err
pure 0
putStrLn $ "[clone_gate] Sealed " ++ show (length entries) ++ " modules -> " ++ manifestPath
pure (length entries)
-- ββ Parse MANIFEST.seal ββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- Minimal JSON field extractor (no external JSON library needed)
extractField : String -> String -> Maybe String
extractField key line =
let needle = "\"" ++ key ++ "\":\""
in case strIndex line (cast (length needle)) of
_ => case split (== needle) line of
[_, rest] => Just (takeWhile (/= '"') rest)
_ => Nothing
parseEntry : String -> Maybe Entry
parseEntry line = do
label <- extractField "label" line
path <- extractField "path" line
digest <- extractField "digest" line
prev <- extractField "prev" line
seal <- extractField "seal" line
pure $ MkEntry label path digest prev seal
-- ββ Verify all registered modules ββββββββββββββββββββββββββββββββββββββββββ
data VerifyResult = OK String | Fail String String String | Missing String
export
verifyAll : HasIO io => String -> io Bool
verifyAll repoRoot = do
Right contents <- readFile manifestPath
| Left _ => do
putStrLn "[clone_gate] MANIFEST.seal not found. Run: clone_gate seal"
pure False
let sealed : List Entry
sealed = mapMaybe parseEntry (lines contents)
let sealedMap : List (String, Entry)
sealedMap = map (\e => (e.path, e)) sealed
results <- traverse (checkOne repoRoot sealedMap) registeredModules
let ok = all isOk results
traverse_ printResult results
pure ok
where
isOk : VerifyResult -> Bool
isOk (OK _) = True
isOk _ = False
checkOne : HasIO io => String -> List (String, Entry) -> String -> io VerifyResult
checkOne root sealedMap rel =
case lookup rel sealedMap of
Nothing => pure (Missing rel)
Just e => do
actual <- sha256File (root ++ "/" ++ rel)
if actual == e.digest
then pure (OK rel)
else pure (Fail rel e.digest actual)
printResult : HasIO io => VerifyResult -> io ()
printResult (OK rel) = putStrLn $ " [OK] " ++ rel
printResult (Missing rel) = putStrLn $ " [MISSING] " ++ rel
printResult (Fail rel exp act) = do
putStrLn $ " [FAIL] " ++ rel
putStrLn $ " expected: " ++ take 32 exp ++ "..."
putStrLn $ " actual : " ++ take 32 act ++ "..."
-- ββ Per-file gate (import-time check) ββββββββββββββββββββββββββββββββββββββ
||| Call at the top of any sovereign module to verify it hasn't been tampered.
||| On failure: prints an error and exits with code 1.
export
gate : HasIO io => String -> String -> io ()
gate repoRoot relPath = do
Right contents <- readFile (repoRoot ++ "/" ++ manifestPath)
| Left _ => do
putStrLn $ "[clone_gate] WARNING: " ++ relPath ++ " β MANIFEST.seal not found (unsealed)"
pure ()
let sealed = mapMaybe parseEntry (lines contents)
case find (\e => e.path == relPath) sealed of
Nothing => putStrLn $ "[clone_gate] WARNING: " ++ relPath ++ " not in manifest"
Just e =>
if isUnsealed e.digest
then putStrLn $ "[clone_gate] WARNING: " ++ relPath ++ " UNSEALED"
else do
actual <- sha256File (repoRoot ++ "/" ++ relPath)
when (actual /= e.digest) $ do
putStrLn $ "\n[clone_gate] INTEGRITY FAILURE: " ++ relPath
putStrLn $ " expected: " ++ e.digest
putStrLn $ " actual : " ++ actual
putStrLn $ " => File modified since last seal. Run: clone_gate seal"
exitFailure
|