1 · Author
Écrivez le YAML à la main ou depuis le modeleur. Convention : project.yml +
models/ + metrics/ + facets/ + joints/ + lexicon/ + contracts/.
La OpenDome Semantic Layer (OSL) est une spécification ouverte (Apache-2.0) qui décrit, en YAML déclaratif, un modèle sémantique couvrant deux plans physiques : tables structurées dans Iceberg et collections vectorielles dans Lance. Une spec, un langage, une chaîne de gouvernance.
Cette page fixe le vocabulaire et le modèle mental. Pour le contrat normatif, voir la spécification ; pour la surface d’API, voir la référence.
Les couches sémantiques OSS d’aujourd’hui (dbt MetricFlow, Cube, WrenAI) décrivent des tables relationnelles. Aucune ne traite les collections vectorielles comme des citoyens de première classe. Cela impose deux modèles parallèles — le sémantique-SQL et « ce que le RAG sait du client » — qui divergent dès le lendemain de leur mise en production.
OSL couvre les deux plans :
JointEntity, un objet logique (Customer, Policy,
Order) qui relie des colonnes Iceberg à des facettes Lance sous une seule clé
primaire.Déclarez une fois. La gouvernance, le lineage, les APIs et les agents consomment tous le même modèle.
Le modèle
OSL décrit un modèle sémantique sur deux substrats physiques avec un seul langage
déclaratif. Chaque plan a ses primitives, sa convention de FQN, son moteur
d’exécution et son chemin d’évolution. La JointEntity est la pièce qui les
compose.
Tables Iceberg, queries SQL, agrégations, joins. Ici OSL passe par MetricFlow sans modification :
semantic_models/*.yml décrivent des entités physiques avec leurs dimensions et measures.metrics/*.yml définissent des métriques certifiées (simple, ratio, cumulative, derived).saved_queries/*.yml empaquettent des requêtes réutilisables.time_spine.yml définit le grain temporel du projet.Le moteur de référence (tenant-semantic-api) compile ces YAML en SQL sur
Trino, exécuté contre Iceberg dans le bucket du tenant.
Collections Lance avec chunks indexés. Ici OSL définit de nouvelles primitives :
facets/*.yml (kind: UnstructuredFacet) décrivent une collection Lance : son
dataset path, chunk schema, modèle d’embedding, index (vector + FTS + scalaire),
champs de metadata, ACL row-level et limites de retrieval.Le moteur exécute un retrieval hybride (vector ⊕ BM25 ⊕ filtre de metadata) sur LanceDB et renvoie des passages avec citation, en respectant l’ACL et les limites déclarées.
joints/*.yml (kind: JointEntity) déclare un objet logique dont la clé
primaire provient d’un semantic_model et qui expose des attributs composés —
certains du plan structuré (colonnes, métriques), d’autres du non structuré
(retrieval scoped par la clé).Le pont est honnête : chaque join_on mappe une clé d’entité sur un champ de
metadata autoritatif et indexé de la facette. Un champ avec provenance: derived ne peut jamais être une clé de join — l’extraction dérivée a lieu à
l’ingest, jamais au query time.
Un agent qui invoque entity_get(Customer, customer_id=X) reçoit, en une seule
réponse, les attributs canoniques de l’entité, les métriques pré-agrégées qui
s’appliquent et les chunks Lance filtrés par customer_id=X. Un appel, une
décision de politique, une trace d’audit.
Bien que la spec ait des primitives séparées (SemanticModel, JointEntity), qui
consomme OSL ne voit qu’un seul mot : Entity. Ce qui distingue une Entity
d’une autre n’est pas son kind mais sa composition :
| Composition | Ce que c’est | D’où ça vient |
|---|---|---|
structured |
Entity purement tabulaire (Iceberg seul) | un SemanticModel qui n’est ni ancre de joint ni bridge |
structured+content |
Colonnes structurées reliées à des facettes Lance | un JointEntity avec des jambes non structurées |
content |
Un corpus Lance autonome, sans ancre structurée | une UnstructuredFacet qu’aucun joint n’absorbe |
Les relations entre Entities viennent de deux endroits : entités MetricFlow
partagées (primary / foreign / unique) pour les voisins structuré ↔
structuré, et N-N via bridge — une table de jonction (SemanticModel avec ≥2
entités foreign) qui se réduit à une arête N-N directe au lieu d’être un nœud.
Ce vocabulaire est celui que sert le domain-map (GET /osl/domain-map) : un
unique node kind entity étiqueté par composition, plus des nœuds metric et candidate.
| Famille | Kind | Objectif | MetricFlow |
|---|---|---|---|
| Modélisation structurée | SemanticModel, Metric, SavedQuery, TimeSpine |
Décrire les tables Iceberg + métriques | ✅ identique / étendu |
| Modélisation non structurée | UnstructuredFacet |
Décrire une collection Lance | ❌ OSL-only |
| Composition | JointEntity |
Relier les deux plans sous une entité logique | ❌ OSL-only |
| Catalogue et contrats | Lexicon, DataContract, PolicyBinding |
Synonymie, SLAs, hints pour le PDP | ❌ OSL-only |
Toute primitive porte apiVersion: osl.opendome.eu/v1, un kind et un name
unique. L’identité d’un objet est (kind, name) — sauf SemanticModel, dont
l’identité est (catalog, schema, table).
OSL déclare ; le moteur exécute. Entre les deux, il y a une étape de compilation.
1 · Author
Écrivez le YAML à la main ou depuis le modeleur. Convention : project.yml +
models/ + metrics/ + facets/ + joints/ + lexicon/ + contracts/.
2 · Validate
osl validate exécute JSON Schema par fichier plus la cross-reference (chaque FQN
référencé doit exister). Erreurs en E0xx (schema) ou E1xx (resolve).
3 · Compile
osl compile émet un unique target/manifest.json — la représentation
canonique et reproductible que le moteur sert et que la policy engine lit.
4 · Publish
osl publish envoie le manifest au moteur du tenant. Atomique (revert avec
osl publish --revert), émet un événement de lineage osl.publish.
À partir de là, le moteur sert les requêtes : REST pour le BI/les apps, tools MCP pour les agents.
OSL est strict sur les noms parce que les politiques en dépendent. Quatre espaces de FQN :
| Espace | Forme | Exemple |
|---|---|---|
| Structuré | <catalog>.<schema>.<table>[:<column>] |
iceberg.curated.customer:email |
| Non structuré | 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 |
Les FQN sont case-sensitive et ne portent pas de version — la version vit dans le
manifest compilé et signé en SHA. La policy engine consomme ces FQNs dans sa
tuple 4-D (data, action, actor, context). OSL n’invente pas une sémantique
d’accès parallèle ; il émet les faits que le PDP indexe.
OSL est la spec partagée entre toutes ces pièces, pas les pièces.