ENCSPožádat o přístup ke kódu

Záznam rozhodnutí 0001

0001 — Oprávnění se vynucují před modelem, v SQL

Stav: přijato · Datum: 2026-08

Kontext

Znalostní báze odpovídá na otázky nad firemními dokumenty. Některé dokumenty jsou pro všechny, jiné (interní poznámky, čísla, smlouvy) jen pro majitele nebo pro konkrétní lidi. Na konci řetězce sedí jazykový model a píše odpověď.

Otázka zní, kde má řízení přístupu žít.

Možnosti

  1. Říct to modelu. Vyhledat široce, předat roli uživatele a modelu nařídit, ať neprozradí zakázaný obsah.
  2. Filtrovat po vyhledání, v Pythonu. Vzít nejlepších k kusů a vyřadit ty, které uživatel vidět nesmí.
  3. Filtrovat přímo ve vyhledávacím dotazu. Každý SQL dotaz, který sahá na kusy dokumentů, připojí dokument a uplatní přístupovou podmínku uživatele.

Rozhodnutí

Možnost 3. accessible_document_clause(user) (app/access.py) vrací podmínku v SQL — sdíleno s celou firmou / patří mně / ACL podle uživatele / ACL podle role — a uplatňuje se v každém dotazu, který čte kusy: ve vektorovém vyhledávání, ve vyhledávání slov, v počtech výskytů slov, které používá filtr relevance, i ve slovníku pro opravu překlepů.

Hodnota „bez filtru“ neexistuje, ani pro majitele firmy. Viditelnost má jeden zdroj pravdy (sharing + owner_id + ACL); all_company se odvozuje ze sharing a omezení CHECK je drží shodné. Osobní položka někoho jiného zůstává jeho — slib, který platí jen tehdy, když z něj nikdo, ani majitel, není vyjmutý. (Starší verze nechávaly majitele filtr obejít a viditelnost určovaly z all_company, zatímco sharing existovalo, ale nečetlo se: dva modely, které se mohly rozejít.)

Proč ne 1: pokyn není kontrola. Dokument s podstrčenými pokyny, otázka položená jinými slovy nebo aktualizace modelu — cokoli z toho může z „prosím, ne“ udělat únik, a jistotu nezískáte žádným množstvím testů. Hůř: model, kterému řeknete „buď opatrný“, to přežene a odmítá i ty, kdo nárok mají — viděli jsme, jak majitel dostával nepředvídatelná odmítnutí, kdykoli prompt zmiňoval důvěrnost.

Proč ne 2: filtrování po výběru nejlepších k potichu zmenší výsledek. Když je 6 z 8 nejlepších kusů zakázaných, uživatel dostane 2 slabé kusy a horší odpověď bez jakéhokoli vysvětlení. Filtr v dotazu řadí jen to, co uživatel vidět smí.

Nenápadnou částí byly počty výskytů slov: kdyby se statistiky „vzácných slov“ počítaly z celého korpusu, filtr relevance by se choval různě podle dokumentů, které uživatel nevidí — postranní kanál. Proto jsou omezené také, stejně jako slovník pro opravu překlepů: oprava „astrolabbe“ na slovo, které existuje jen v cizím dokumentu, by prozradila, že to slovo existuje.

Cena

  • Každý vyhledávací dotaz nese spojení s přístupovými pravidly; s indexem nad document_acl(document_id) je to v tomto měřítku levné, ale ne zadarmo.
  • Prompt modelu říká opak možnosti 1: vše, co jsi dostal, uživatel vidět smí — odpověz úplně. Bezpečné je to jen díky filtru před modelem. Tato dvě rozhodnutí spolu souvisejí; změnit jedno bez druhého je chyba.
  • Logika přístupu žije v jediné funkci. Každý nový způsob čtení kusů jí musí projít.
  • Tvrzení je otestované tam, kde na tom záleží: nad skutečným Postgresem s pgvector, skutečnými dotazy (tests/integration/test_acl_pg.py) — vektorové hledání, hledání slov, df a velikost korpusu, slovník, celá odpovědní cesta i endpointy dokumentů, pro pět členů s různými právy. Běh s vynuceným selháním (scripts/forced_fail.py) nechá filtr pustit všechno a zvlášť odebere omezení z jednotlivých dotazů — a vyžaduje, aby tyto testy spadly.