Salta ai contenuti

OSL — lo strato di consumo

La OpenDome Semantic Layer (OSL) è una specifica aperta (Apache-2.0) che descrive, in YAML dichiarativo, un modello semantico che abbraccia due piani fisici: tabelle strutturate in Iceberg e collezioni vettoriali in Lance. Una spec, un linguaggio, una catena di governance.

Questa pagina fissa il vocabolario e il modello mentale. Per il contratto normativo vedi la specifica; per la superficie di API vedi il riferimento.

Gli strati semantici OSS di oggi (dbt MetricFlow, Cube, WrenAI) descrivono tabelle relazionali. Nessuno tratta le collezioni vettoriali come cittadini di prima classe. Questo costringe a due modelli paralleli — quello semantico-SQL e “ciò che il RAG sa del cliente” — che divergono il giorno dopo averli messi in produzione.

OSL abbraccia entrambi i piani:

  1. Strutturato — entità, metriche, dimensioni, contratti di dati. Qui OSL è un superset di dbt MetricFlow: qualsiasi progetto MetricFlow valido è OSL valido.
  2. Non strutturato — collezioni Lance con il loro chunk schema, embedding, indici, filtri di metadata e ACL row-level. L’estensione OSL-only.
  3. Il ponte — la JointEntity, un oggetto logico (Customer, Policy, Order) che unisce colonne di Iceberg a facet Lance sotto un’unica chiave.

Dichiari una volta. La governance, il lineage, le API e gli agenti consumano tutti lo stesso modello.

Il modello

STRUTTURATO · ICEBERGNON STRUTTURATO · LANCEJOINTENTITY
Un'unica spec dichiarativa abbraccia entrambi i piani fisici; la JointEntity li unisce sotto un'unica chiave.

OSL descrive un modello semantico su due substrati fisici con un solo linguaggio dichiarativo. Ogni piano ha le sue primitive, la sua convenzione di FQN, il suo motore di esecuzione e il suo percorso di evoluzione. La JointEntity è il pezzo che li compone.

Tabelle Iceberg, query SQL, aggregazioni, join. Qui OSL passa attraverso MetricFlow senza modifiche:

  • semantic_models/*.yml descrivono entità fisiche con le loro dimensioni e measure.
  • metrics/*.yml definiscono metriche certificate (simple, ratio, cumulative, derived).
  • saved_queries/*.yml impacchettano query riutilizzabili.
  • time_spine.yml definisce la granularità temporale del progetto.

Il motore di riferimento (tenant-semantic-api) compila questi YAML in SQL su Trino, eseguito contro Iceberg nel bucket del tenant.

Collezioni Lance con chunk indicizzati. Qui OSL definisce nuove primitive:

  • facets/*.yml (kind: UnstructuredFacet) descrivono una collezione Lance: il suo dataset path, chunk schema, modello di embedding, indici (vector + FTS + scalare), campi di metadata, ACL row-level e limiti di retrieval.

Il motore esegue retrieval ibrido (vector ⊕ BM25 ⊕ filtro di metadata) su LanceDB e restituisce passages con citazione, rispettando l’ACL e i limiti dichiarati.

  • joints/*.yml (kind: JointEntity) dichiara un oggetto logico la cui chiave primaria proviene da un semantic_model e che espone attributi composti — alcuni dal piano strutturato (colonne, metriche), altri dal non strutturato (retrieval scoped per la chiave).

Il ponte è onesto: ogni join_on mappa una chiave di entità a un campo di metadata autorevole e indicizzato del facet. Un campo con provenance: derived non può mai essere una chiave di join — l’estrazione derivata avviene in ingest, mai in query time.

Un agente che invoca entity_get(Customer, customer_id=X) riceve, in un’unica risposta, gli attributi canonici dell’entità, le metriche pre-aggregate applicabili e i chunk Lance filtrati per customer_id=X. Una chiamata, una decisione di policy, una traccia di audit.

Anche se la spec ha primitive separate (SemanticModel, JointEntity), chi consuma OSL vede una sola parola: Entity. Ciò che distingue una Entity da un’altra non è il suo kind ma la sua composizione:

Composizione Cos’è Da dove arriva
structured Entity puramente tabellare (solo Iceberg) un SemanticModel che non è né ancora di joint né ponte
structured+content Colonne strutturate unite a facet Lance un JointEntity con gambe non strutturate
content Un corpus Lance autonomo, senza ancora strutturata un UnstructuredFacet che nessun joint assorbe

Le relazioni tra Entity arrivano da due posti: entità MetricFlow condivise (primary / foreign / unique) per i vicini strutturato ↔ strutturato, e N-N tramite ponte — una tabella di giunzione (SemanticModel con ≥2 entità foreign) che collassa a un arco N-N diretto invece di essere un nodo.

Questo vocabolario è ciò che serve il domain-map (GET /osl/domain-map): un unico node kind entity etichettato per composizione, più nodi metric e candidate.

Famiglia Kind Scopo MetricFlow
Modellazione strutturata SemanticModel, Metric, SavedQuery, TimeSpine Descrivere tabelle Iceberg + metriche ✅ identico / esteso
Modellazione non strutturata UnstructuredFacet Descrivere una collezione Lance ❌ OSL-only
Composizione JointEntity Unire entrambi i piani sotto un’unica entità logica ❌ OSL-only
Catalogo e contratti Lexicon, DataContract, PolicyBinding Sinonimi, SLA, hint per il PDP ❌ OSL-only

Ogni primitiva porta apiVersion: osl.opendome.eu/v1, un kind e un name univoco. L’identità di un oggetto è (kind, name) — tranne SemanticModel, la cui identità è (catalog, schema, table).

OSL dichiara; il motore esegue. Tra i due c’è un passo di compilazione.

1 · Author

Scrivi YAML a mano o dal modeler. Convenzione: project.yml + models/ + metrics/ + facets/ + joints/ + lexicon/ + contracts/.

2 · Validate

osl validate esegue JSON Schema per file più il cross-reference (ogni FQN referenziato deve esistere). Errori in E0xx (schema) o E1xx (resolve).

3 · Compile

osl compile emette un unico target/manifest.json — la rappresentazione canonica e riproducibile che il motore serve e la policy engine legge.

4 · Publish

osl publish carica il manifest sul motore del tenant. Atomico (revert con osl publish --revert), emette un evento di lineage osl.publish.

Da lì il motore serve le query: REST per BI/app, tool MCP per gli agenti.

OSL è rigoroso sui nomi perché le policy dipendono da essi. Quattro spazi di FQN:

Spazio Forma Esempio
Strutturato <catalog>.<schema>.<table>[:<column>] iceberg.curated.customer:email
Non strutturato 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

Gli FQN sono case-sensitive e non portano versione — la versione vive nel manifest compilato e firmato con SHA. La policy engine consuma questi FQN nella sua tupla 4-D (data, action, actor, context). OSL non inventa una semantica di accesso parallela; emette i fatti che il PDP indicizza.

  • Non è un motore SQL. Lo è Trino; OSL gli dà significato.
  • Non è un orchestratore. Dagster esegue i Job di ingest/embedding.
  • Non è un database vettoriale. Lo è LanceDB; OSL descrive le collezioni.
  • Non è un IAM. Lo è OPA / il cell-model; OSL emette i fatti che il PDP consuma.
  • Non è un tool di BI. Metabase, Superset, Streamlit consumano OSL.
  • Non è un agent framework. LangGraph, server MCP consumano OSL.

OSL è la spec condivisa tra tutti questi pezzi, non i pezzi.