Skip to content

OSL — the consumption layer

The OpenDome Semantic Layer (OSL) is an open specification (Apache-2.0) that describes, in declarative YAML, a semantic model spanning two physical planes: structured tables in Iceberg and vector collections in Lance. One spec, one language, one governance chain.

This page fixes the vocabulary and the mental model. For the normative contract see the specification; for the API surface see the API reference.

Today’s OSS semantic layers (dbt MetricFlow, Cube, WrenAI) describe relational tables. None treats vector collections as first-class. That forces two parallel models — the SQL-semantic one and “what the RAG knows about the customer” — which diverge the day after they ship.

OSL spans both planes:

  1. Structured — entities, metrics, dimensions, data contracts. Here OSL is a superset of dbt MetricFlow: any valid MetricFlow project is valid OSL.
  2. Unstructured — Lance collections with their chunk schema, embeddings, indexes, metadata filters and row-level ACL. The OSL-only extension.
  3. The bridge — the JointEntity, a logical object (Customer, Policy, Order) that joins Iceberg columns to Lance facets under one primary key.

Declare once. Governance, lineage, APIs and agents all consume the same model.

The model

STRUCTURED · ICEBERGUNSTRUCTURED · LANCEJOINTENTITY
One declarative spec spans both physical planes; the JointEntity bridges them under one key.

OSL describes a semantic model over two physical substrates with one declarative language. Each plane has its own primitives, FQN convention, execution engine and evolution path. The JointEntity is the piece that composes them.

Iceberg tables, SQL queries, aggregations, joins. Here OSL passes MetricFlow through unchanged:

  • semantic_models/*.yml describe physical entities with their dimensions and measures.
  • metrics/*.yml define certified metrics (simple, ratio, cumulative, derived).
  • saved_queries/*.yml package reusable queries.
  • time_spine.yml defines the project’s time grain.

The reference engine (tenant-semantic-api) compiles these YAML to SQL over Trino, executed against Iceberg in the tenant’s bucket.

Lance collections with indexed chunks. Here OSL defines new primitives:

  • facets/*.yml (kind: UnstructuredFacet) describe a Lance collection: dataset path, chunk schema, embedding model, indexes (vector + FTS + scalar), metadata fields, row-level ACL and retrieval limits.

The engine runs hybrid retrieval (vector ⊕ BM25 ⊕ metadata filter) over LanceDB and returns passages with citation, honoring the declared ACL and limits.

  • joints/*.yml (kind: JointEntity) declares a logical object whose primary key comes from a semantic_model and which exposes composed attributes — some from the structured plane (columns, metrics), some from the unstructured plane (retrieval scoped by the key).

The bridge is honest: each join_on maps an entity key to an authoritative, indexed metadata field of the facet. A metadata field with provenance: derived can never be a join key — derived extraction happens at ingest, never at query time.

An agent calling entity_get(Customer, customer_id=X) receives, in one response, the entity’s canonical attributes, the applicable pre-aggregated metrics, and the Lance chunks filtered by customer_id=X. One call, one policy decision, one audit trail.

Although the spec has separate primitives (SemanticModel, JointEntity), the consumer sees one word: Entity. What distinguishes one Entity from another isn’t its kind but its composition:

Composition What it is Where it comes from
structured Purely tabular Entity (Iceberg only) a SemanticModel that is neither a joint anchor nor a bridge
structured+content Tabular columns joined to Lance facets a JointEntity with unstructured legs
content A standalone Lance corpus, no structured anchor an UnstructuredFacet no joint absorbs

Relations between Entities come from two places: shared MetricFlow entities (primary / foreign / unique) for structured ↔ structured neighbours, and N-N via a bridge — a junction table (SemanticModel with ≥2 foreign entities) that collapses to a direct N-N edge rather than being a node.

This vocabulary is what the domain-map (GET /osl/domain-map) serves: a single entity node kind tagged by composition, plus metric nodes and candidate nodes.

Family Kind Purpose MetricFlow
Structured modeling SemanticModel, Metric, SavedQuery, TimeSpine Describe Iceberg tables + metrics ✅ identical / extended
Unstructured modeling UnstructuredFacet Describe a Lance collection ❌ OSL-only
Composition JointEntity Join both planes under one logical entity ❌ OSL-only
Catalog & contracts Lexicon, DataContract, PolicyBinding Synonyms, SLAs, hints for the PDP ❌ OSL-only

Every primitive carries apiVersion: osl.opendome.eu/v1, a kind, and a unique name. An object’s identity is (kind, name) — except SemanticModel, whose identity is (catalog, schema, table).

OSL declares; the engine executes. Between them is a compilation step.

1 · Author

Write YAML by hand or from the modeler. Convention: project.yml + models/

  • metrics/ + facets/ + joints/ + lexicon/ + contracts/.

2 · Validate

osl validate runs JSON Schema per file plus cross-reference (every referenced FQN must exist). Errors in E0xx (schema) or E1xx (resolve).

3 · Compile

osl compile emits a single target/manifest.json — the canonical, reproducible representation the engine serves and the policy engine reads.

4 · Publish

osl publish uploads the manifest to the tenant’s engine. Atomic (revert with osl publish --revert), emits an osl.publish lineage event.

From there the engine serves queries: REST for BI/apps, MCP tools for agents.

OSL is strict about names because policies depend on them. Four FQN spaces:

Space Form Example
Structured <catalog>.<schema>.<table>[:<column>] iceberg.curated.customer:email
Unstructured 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

FQNs are case-sensitive and carry no version — versioning lives in the compiled, SHA-signed manifest. The policy engine consumes these FQNs in its 4-D tuple (data, action, actor, context). OSL doesn’t invent a parallel access semantics; it emits the facts the PDP indexes.

  • Not a SQL engine. Trino is; OSL gives it meaning.
  • Not a pipeline orchestrator. Dagster runs ingest/embedding Jobs.
  • Not a vector database. LanceDB is; OSL describes the collections.
  • Not an IAM. OPA / the cell-model is; OSL emits facts the PDP consumes.
  • Not a BI tool. Metabase, Superset, Streamlit consume OSL.
  • Not an agent framework. LangGraph, MCP servers consume OSL.

OSL is the shared spec between all those pieces, not the pieces.