Chaque lecture via /osl/query est limitée par un
modèle de capacité + permission : qui demande (une chaîne de sujets en
délégation), ce que chaque politique leur accorde sur le graphe sémantique, et
comment ces grants se composent. Il est fail-closed par construction — le
défaut est deny, et tout ce qui reste non résolu est écarté, jamais deviné.
L’accès est évalué sur une chaîne de principals, racine → feuille. Le sujet
agissant est le dernier maillon. La composition a deux niveaux :
Au sein d’un principal
Toutes les politiques liant ce principal se combinent : les grants font
union, l’obligation la plus forte l’emporte (deny > mask > redact),
un deny explicite prime, et les conditions (plage d’IP, fenêtre horaire) ne
conditionnent que l’ALLOW — un deny est inconditionnel.
À travers la chaîne
Les maillons se combinent par MEET (intersection) : une entité ou une
colonne n’est accordée que si chaque maillon l’accorde. Le plafond de tier
est le minimum (le plus restrictif) et le caviardage requis est l’union de ce
que chaque maillon exige. Un maillon vide ou non rédigé réduit le meet à deny.
Le scope sémantique d’une politique est un ensemble de bindings, chacun
indexé par un nodeId du domain-map (p. ex.
entity:Customer, metric:aum). Un binding déclare :
Champ
Signification
attributeGrants[]
Grant par attribut : cleartext, mask, ou deny
filters[] (+ filtersBool)
Prédicats de lignes qui délimitent quelles lignes sont visibles
Les grants sont positifs : une colonne qui n’est pas accordée est caviardée,
jamais renvoyée comme NULL ni silencieusement retirée du schema. deny retire
entièrement une colonne de la projection ; un deny global à la politique prime
sur le grant de tout autre binding.
Les facettes de contenu sont gouvernées par des versions de caviardage
nommées — un découplage entre un nom orienté humain et une identité physique,
stable à l’octet :
Une RedactionVersion a un nom (la poignée console, p. ex. "PII básica")
et un ensemble de tags (les classes de contenu qu’elle caviarde, p. ex.
{email, phone}).
Les tags sont l’identité physique (inchangée à l’octet, utilisée comme clé de
table) ; le nom n’est que pour l’authoring.
La version implicite original (sans tags) est la table de base.
Un facetGrant lie un nom de version requis par facette. La sélection est
exact-or-deny : le moteur sert la version stockée dont les tags matchent
exactement, ou refuse la facette — il ne se rabat jamais sur une version plus ou
moins caviardée.
À travers une chaîne, les versions requises se composent par union des
ensembles de tags (plus de caviardage est toujours sûr).
L’application est server-side, dans la cellule. Les résultats :
Situation
Valeur renvoyée
Dans le résultat ?
cleartext accordé
original
oui
Obligation mask
***
oui
Obligation redact
⟦redacted⟧
oui (survit pour ORDER BY/GROUP BY)
deny / non accordé
—
retiré de la projection
La sentinelle⟦redacted⟧ signifie que la colonne survit avec une valeur
gouvernée (donc la shape downstream est stable), plutôt que de devenir NULL ou de
disparaître — c’est ce qui rend les joins cross-modaux fail-closed et le modèle
composable.
La chaîne n’est pas un en-tête de confiance — c’est un token signé. La cellule
génère et vérifie un JWT HS256 dont le payload contient la chaîne elle-même, de
sorte qu’un appelant ne peut ni la raccourcir ni la falsifier sans casser la
signature (une chaîne absente/invalide → deny). Voir
Authentification.
Deux opérations en dry-run calculent la composition réelle sans servir de données
(la console Enterprise les expose ; les APIs sont dans la cellule) :
:simulate — prévisualise une politique contre une query en tant que sujet
unique.
:simulate-chain — compile l’ensemble complet des politiques rédigées du
tenant sur une chaîne root → leaf (le MEET), renvoyant le scope composé plus
une ventilation de la contribution par maillon. Un maillon non rédigé est
signalé ; le meet se vide en un empty_meet fail-closed.