Ir al contenido

OSL — la capa de consumo

La OpenDome Semantic Layer (OSL) es una especificación abierta (Apache-2.0) que describe, en YAML declarativo, un modelo semántico que abarca dos planos físicos: tablas estructuradas en Iceberg y colecciones vectoriales en Lance. Una spec, un lenguaje, una cadena de gobernanza.

Esta página fija el vocabulario y el modelo mental. Para el contrato normativo ver la especificación; para la superficie de API ver la referencia.

Las capas semánticas OSS de hoy (dbt MetricFlow, Cube, WrenAI) describen tablas relacionales. Ninguna trata las colecciones vectoriales como ciudadanos de primera clase. Eso fuerza dos modelos paralelos — el semántico-SQL y “lo que el RAG sabe del cliente” — que divergen al día siguiente de ponerlos en producción.

OSL abarca ambos planos:

  1. Estructurado — entidades, métricas, dimensiones, contratos de datos. Aquí OSL es un superset de dbt MetricFlow: cualquier proyecto MetricFlow válido es OSL válido.
  2. No estructurado — colecciones Lance con su chunk schema, embeddings, índices, filtros de metadata y ACL row-level. La extensión OSL-only.
  3. El puente — la JointEntity, un objeto lógico (Customer, Policy, Order) que une columnas de Iceberg con facetas Lance bajo una sola clave.

Declaras una vez. La gobernanza, el lineage, las APIs y los agentes consumen el mismo modelo.

El modelo

ESTRUCTURADO · ICEBERGNO ESTRUCTURADO · LANCEJOINTENTITY
Una sola spec declarativa abarca ambos planos físicos; la JointEntity los une bajo una sola clave.

OSL describe un modelo semántico sobre dos sustratos físicos con un solo lenguaje declarativo. Cada plano tiene sus primitivas, su convención de FQN, su motor de ejecución y su path de evolución. La JointEntity es la pieza que los compone.

Tablas Iceberg, queries SQL, agregaciones, joins. Aquí OSL pasa por MetricFlow sin modificaciones:

  • semantic_models/*.yml describen entidades físicas con sus dimensiones y measures.
  • metrics/*.yml definen métricas certificadas (simple, ratio, cumulative, derived).
  • saved_queries/*.yml empaquetan consultas reusables.
  • time_spine.yml define el grano temporal del proyecto.

El motor de referencia (tenant-semantic-api) compila estas YAML a SQL sobre Trino, ejecutado contra Iceberg en el bucket del tenant.

Colecciones Lance con chunks indexados. Aquí OSL define primitivas nuevas:

  • facets/*.yml (kind: UnstructuredFacet) describe una colección Lance: su dataset path, chunk schema, modelo de embedding, índices (vector + FTS + escalar), campos de metadata, ACL row-level y límites de retrieval.

El motor ejecuta retrieval híbrido (vector ⊕ BM25 ⊕ filtro de metadata) sobre LanceDB y devuelve passages con citación, respetando la ACL y los límites declarados.

  • joints/*.yml (kind: JointEntity) declara un objeto lógico cuya clave primaria proviene de un semantic_model y que expone atributos compuestos — unos del plano estructurado (columnas, métricas), otros del no estructurado (retrieval scoped por la clave).

El puente es honesto: cada join_on mapea una clave de entidad a un campo de metadata autoritativo e indexado de la faceta. Un campo con provenance: derived nunca puede ser clave de join — la extracción derivada ocurre en ingest, jamás en query time.

Un agente que invoca entity_get(Customer, customer_id=X) recibe, en una sola respuesta, los atributos canónicos de la entidad, las métricas pre-agregadas que apliquen y los chunks Lance filtrados por customer_id=X. Una llamada, una decisión de política, una traza de auditoría.

Aunque la spec tenga primitivas separadas (SemanticModel, JointEntity), quien consume OSL ve una sola palabra: Entity. Lo que distingue una Entity de otra no es su kind sino su composición:

Composición Qué es De dónde sale
structured Entity puramente tabular (solo Iceberg) un SemanticModel que no es ancla de joint ni bridge
structured+content Columnas estructuradas unidas a facetas Lance un JointEntity con piernas no estructuradas
content Un corpus Lance autónomo, sin ancla estructurada una UnstructuredFacet que ningún joint absorbe

Las relaciones entre Entities salen de dos sitios: entidades MetricFlow compartidas (primary / foreign / unique) para vecinos estructurado ↔ estructurado, y N-N vía bridge — una tabla puente (SemanticModel con ≥2 entidades foreign) que colapsa a una arista N-N directa en vez de ser un nodo.

Este vocabulario es el que sirve el domain-map (GET /osl/domain-map): un único node kind entity etiquetado por composición, más nodos metric y candidate.

Familia Kind Propósito MetricFlow
Modelado estructurado SemanticModel, Metric, SavedQuery, TimeSpine Describir tablas Iceberg + métricas ✅ idéntico / extendido
Modelado no estructurado UnstructuredFacet Describir una colección Lance ❌ OSL-only
Composición JointEntity Unir ambos planos bajo una entidad lógica ❌ OSL-only
Catálogo y contratos Lexicon, DataContract, PolicyBinding Sinonimia, SLAs, hints para la PDP ❌ OSL-only

Toda primitiva lleva apiVersion: osl.opendome.eu/v1, un kind y un name único. La identidad de un objeto es (kind, name) — salvo SemanticModel, cuya identidad es (catalog, schema, table).

OSL declara; el motor ejecuta. Entre ambos hay un paso de compilación.

1 · Author

Escribe YAML a mano o desde el modelador. Convención: project.yml + models/ + metrics/ + facets/ + joints/ + lexicon/ + contracts/.

2 · Validate

osl validate corre JSON Schema por archivo más cross-reference (cada FQN referenciado debe existir). Errores en E0xx (schema) o E1xx (resolve).

3 · Compile

osl compile emite un único target/manifest.json — la representación canónica y reproducible que el motor sirve y la policy engine lee.

4 · Publish

osl publish sube el manifest al motor del tenant. Atómico (revert con osl publish --revert), emite un evento de lineage osl.publish.

A partir de ahí el motor sirve consultas: REST para BI/apps, tools MCP para agentes.

OSL es estricto con los nombres porque las políticas dependen de ellos. Cuatro espacios de FQN:

Espacio Forma Ejemplo
Estructurado <catalog>.<schema>.<table>[:<column>] iceberg.curated.customer:email
No estructurado lance://<collection>[/*][:<metadata_field>] lance://meeting_transcripts:attendees
Tools <tool_kind>:<resource> metrics:revenue, joints:Customer
Joint-exposed joint://<Entity>.<attribute> joint://Customer.recent_meeting_topics

Los FQN son case-sensitive y no llevan versión — la versionada vive en el manifest compilado y firmado con SHA. La policy engine consume estos FQNs en su tupla 4-D (data, action, actor, context). OSL no inventa una semántica de acceso paralela; emite los hechos que la PDP indexa.

  • No es un motor SQL. Trino lo es; OSL le da significado.
  • No es un orquestador. Dagster ejecuta los Jobs de ingest/embedding.
  • No es una base vectorial. LanceDB lo es; OSL describe las colecciones.
  • No es un IAM. OPA / cell-model lo es; OSL emite hechos que la PDP consume.
  • No es un BI tool. Metabase, Superset, Streamlit consumen OSL.
  • No es un agent framework. LangGraph, MCP servers consumen OSL.

OSL es la spec compartida entre todas esas piezas, no las piezas.