;; search federation + ACL — Haskell source fragment. Depends on index + rank. ;; Federation merges per-peer INDICES (not ranked results): each peer's local ;; DocIds are relabelled to global ids `gid peer local = peer*1000 + local` ;; (dedupe by (peer,doc-id) is automatic via the bijection), then posting lists ;; are unioned per term. Ranking then runs once over the merged index, which is ;; rank-correct. ACL is a post-rank filter: an injected `permit :: DocId -> Bool` ;; predicate (viewer baked in by the caller) — never baked into the index. ;; fedIndex :: [(PeerId, Index)] -> Index ;; aclFilter :: (DocId -> Bool) -> [DocId] -> [DocId] ;; searchTfIdfAcl :: (DocId -> Bool) -> [Term] -> Index -> [DocId] ;; topNTfIdfAcl :: Int -> (DocId -> Bool) -> [Term] -> Index -> [DocId] ;; searchBm25Acl :: (DocId -> Bool) -> Float -> Float -> [Term] -> Index -> [DocId] (define search/fed-src "gid peer local = peer * 1000 + local\nfedRelabelPosting peer p = (gid peer (fst p), snd p)\nfedRelabelEntry peer e = (fst e, map (fedRelabelPosting peer) (snd e))\nfedRelabelIndex peer idx = map (fedRelabelEntry peer) idx\nfedInsP p [] = [p]\nfedInsP p (q:qs) = if fst p < fst q then p : q : qs else if fst p == fst q then p : qs else q : fedInsP p qs\nfedMergePL a b = foldr fedInsP b a\nfedInsTerm t pl [] = [(t, pl)]\nfedInsTerm t pl (x:xs) = if t < fst x then (t, pl) : x : xs else if t == fst x then (fst x, fedMergePL pl (snd x)) : xs else x : fedInsTerm t pl xs\nfedMergeEntry idx e = fedInsTerm (fst e) (snd e) idx\nfedMergeTwo a b = foldl fedMergeEntry a b\nfedAddPeer acc pair = fedMergeTwo acc (fedRelabelIndex (fst pair) (snd pair))\nfedIndex pairs = foldl fedAddPeer emptyIndex pairs\naclFilter permit docs = filter permit docs\nsearchTfIdfAcl permit ts idx = aclFilter permit (rankTfIdf ts idx)\ntopNTfIdfAcl n permit ts idx = take n (aclFilter permit (rankTfIdf ts idx))\nsearchBm25Acl permit k1 b ts idx = aclFilter permit (rankBm25 k1 b ts idx)\n")