Cada lectura a través de /osl/query está limitada
por un modelo de capacidad + permiso: quién pregunta (una cadena de
delegación de sujetos), qué le concede cada política sobre el grafo semántico,
y cómo componen esos grants. Es fail-closed por construcción — el default
es denegar, y cualquier cosa sin resolver se descarta, nunca se adivina.
El acceso se evalúa sobre una cadena de principals, raíz → hoja. El sujeto que
actúa es el último eslabón. La composición tiene dos niveles:
Dentro de un principal
Todas las políticas que vinculan a ese principal se combinan: los grants se
unen, gana la obligación más fuerte (deny > mask > redact), un deny
explícito prevalece, y las condiciones (rango de IP, ventana de tiempo)
solo condicionan el ALLOW — un deny es incondicional.
A lo largo de la cadena
Los eslabones se combinan por MEET (intersección): una entidad o columna
se concede solo si cada eslabón la concede. El tope de tier es el mínimo
(el más restrictivo) y la redacción requerida es la unión de lo que exige cada
eslabón. Un eslabón vacío o sin autorizar colapsa el meet a deny.
El scope semántico de una política es un conjunto de bindings, cada uno
indexado por un nodeId del domain-map (p. ej.
entity:Customer, metric:aum). Un binding declara:
Campo
Significado
attributeGrants[]
Grant por atributo: cleartext, mask o deny
filters[] (+ filtersBool)
Predicados de fila que acotan qué filas son visibles
Los grants son positivos: una columna sin grant se redacta, nunca se devuelve
como NULL ni se descarta silenciosamente del schema. deny elimina una columna
de la proyección por completo; un deny global de política prevalece sobre el
grant de cualquier otro binding.
Las facetas de contenido se gobiernan por versiones de redacción con nombre —
un desacople entre un nombre de cara al humano y una identidad física y estable a
nivel de bytes:
Una RedactionVersion tiene un nombre (el handle de la consola, p. ej.
"PII básica") y un conjunto de tags (las clases de contenido que redacta,
p. ej. {email, phone}).
Los tags son la identidad física (sin cambio de bytes, usados como clave de
tabla); el nombre es solo para la autoría.
La versión implícita original (sin tags) es la tabla base.
Un facetGrant vincula un nombre de versión requerida por faceta. La
selección es exacta-o-deny: el motor sirve la versión almacenada cuyos tags
coinciden exactamente, o deniega la faceta — nunca recurre a una versión más o
menos redactada.
A lo largo de una cadena, las versiones requeridas componen por unión de
conjuntos de tags (más redacción siempre es seguro).
La aplicación es server-side, en la celda. Los resultados:
Situación
Valor devuelto
¿En el resultado?
cleartext concedido
original
sí
obligación mask
***
sí
obligación redact
⟦redacted⟧
sí (sobrevive para ORDER BY/GROUP BY)
deny / sin grant
—
descartada de la proyección
El centinela⟦redacted⟧ significa que la columna sobrevive con un valor
gobernado (así la forma downstream es estable), en vez de convertirse en NULL o
desaparecer — que es lo que hace fail-closed a los joins cross-modal y componible
al modelo.
La cadena no es una cabecera confiada — es un token firmado. La celda acuña y
verifica un JWT HS256 cuyo payload contiene la propia cadena, de modo que un
llamante no puede acortarla ni falsificarla sin romper la firma (una cadena
ausente/inválida → deny). Ver Autenticación.
Dos operaciones dry-run calculan la composición real sin servir datos (la consola
Enterprise las expone; las APIs están en la celda):
:simulate — previsualiza una política contra una consulta como un solo
sujeto.
:simulate-chain — compila el conjunto completo de políticas autorizadas del
tenant sobre una cadena root → leaf (el MEET), devolviendo el scope
compuesto más un desglose de contribución por eslabón. Un eslabón sin
autorizar se marca; el meet se vacía a un empty_meet fail-closed.