Jeder Lesezugriff über /osl/query wird durch ein
Capability-+-Permission-Modell begrenzt: wer fragt (eine Delegations-Kette
aus Subjects), was jede Policy ihnen über den semantischen Graphen gewährt und
wie diese Grants komponieren. Es ist fail-closed per Konstruktion — der
Default ist deny, und alles Unaufgelöste wird verworfen, niemals geraten.
Zugriff wird über eine Kette von Principals ausgewertet, Root → Leaf. Das
handelnde Subject ist das letzte Glied. Die Komposition hat zwei Ebenen:
Innerhalb eines Principals
Alle Policies, die diesen Principal binden, kombinieren sich: Grants
vereinigen sich (Union), die stärkste Obligation gewinnt
(deny > mask > redact), ein expliziter Deny überschreibt, und
Bedingungen (IP-Range, Zeitfenster) gaten nur ALLOW — ein Deny ist unbedingt.
Über die Kette hinweg
Die Glieder kombinieren sich durch MEET (Schnittmenge): eine Entity oder
Spalte ist nur dann gewährt, wenn jedes Glied sie gewährt. Der Tier-Cap
ist das Minimum (am restriktivsten) und die geforderte Redaktion ist die Union
dessen, was jedes Glied verlangt. Ein leeres Glied oder eines ohne verfasste
Policy kollabiert das Meet zu deny.
Der semantische Scope einer Policy ist eine Menge von Bindings, jedes mit
einer Domain-Map-nodeId als Schlüssel
(z. B. entity:Customer, metric:aum). Ein Binding deklariert:
Feld
Bedeutung
attributeGrants[]
Grant pro Attribut: cleartext, mask oder deny
filters[] (+ filtersBool)
Zeilen-Prädikate, die scopen, welche Zeilen sichtbar sind
Grants sind positiv: Eine Spalte, die nicht gewährt ist, wird redigiert,
niemals als NULL zurückgegeben oder stillschweigend aus dem Schema entfernt.
deny entfernt eine Spalte vollständig aus der Projektion; ein policy-globaler
deny überschreibt den Grant jedes anderen Bindings.
Content-Facetten werden durch benannte Redaktionsversionen gesteuert — eine
Entkopplung eines menschenlesbaren Namens von einer physischen, byte-stabilen
Identität:
Eine RedactionVersion hat einen Namen (das Konsolen-Handle, z. B.
"PII básica") und eine Menge von Tags (die Content-Klassen, die sie
redigiert, z. B. {email, phone}).
Die Tags sind die physische Identität (byte-unverändert, als Tabellenschlüssel
genutzt); der Name ist nur fürs Authoring.
Die implizite original-Version (keine Tags) ist die Basistabelle.
Ein facetGrant bindet einen geforderten Versionsnamen pro Facette. Die
Auswahl ist exact-or-deny: Die Engine bedient die gespeicherte Version, deren
Tags exakt übereinstimmen, oder verweigert die Facette — sie fällt niemals auf
eine stärker oder schwächer redigierte Version zurück.
Über eine Kette hinweg komponieren die geforderten Versionen durch Union der
Tag-Mengen (mehr Redaktion ist immer sicher).
Die Durchsetzung ist serverseitig, in der Zelle. Die Ergebnisse:
Situation
Zurückgegebener Wert
Im Ergebnis?
Als cleartext gewährt
Original
ja
mask-Obligation
***
ja
redact-Obligation
⟦redacted⟧
ja (überlebt für ORDER BY/GROUP BY)
deny / nicht gewährt
—
entfernt aus der Projektion
Das ⟦redacted⟧-Sentinel bedeutet, dass die Spalte überlebt mit einem
gesteuerten Wert (sodass die nachgelagerte Form stabil ist), statt zu NULL zu
werden oder zu verschwinden — genau das macht cross-modale Joins fail-closed und
das Modell komponierbar.
Die Kette ist kein vertrauter Header — sie ist ein signiertes Token. Die Zelle
prägt und verifiziert ein HS256-JWT, dessen Payload die Kette selbst enthält,
sodass ein Aufrufer sie nicht kürzen oder fälschen kann, ohne die Signatur zu
brechen (eine fehlende/ungültige Kette → deny). Siehe
Authentifizierung.
Zwei Dry-Run-Operationen berechnen die echte Komposition, ohne Daten zu bedienen
(die Enterprise-Konsole macht sie zugänglich; die APIs liegen in der Zelle):
:simulate — eine Policy gegen eine Query als einzelnes Subject vorschauen.
:simulate-chain — das vollständige verfasste Policy-Set des Tenants über
eine root → leaf-Kette kompilieren (das MEET) und den komponierten Scope
plus eine per-Glied-Beitrags-Aufschlüsselung zurückgeben. Ein Glied ohne
verfasste Policy wird markiert; das Meet leert sich zu einem fail-closed
empty_meet.