Skip to content

Semantic Server

The server is the query path. It holds no durable state, writes nothing to the engine, and is the only component that reads table data. It watches compiled models from ConfigMaps and may use an optional shared cache, so replicas are interchangeable and scale horizontally.

No SQL exists until identity, external authorization, and built-in governance have passed.

CallersData plane · in clusterQuery engineAI agentMCP · /mcpApplicationREST · /v1BI toolSQL, via viewsserverauthenticateplan + governexecute× N replicasConfigMapcompiled artifactValkeyoptionalOPA · Rangeroptional PDPModel tablesSELECT only401 / 403 gatewatchcache, nevercorrectnessauthorizeone governed SELECTper requestBI reads governed views directly on the engine
The server authenticates the caller, obtains any external OPA or Ranger decision, applies built-in model governance, and only then generates SQL.

The caller is resolved to a principal, groups, and roles before anything else happens, in one of two modes that differ by security boundary.

Mode Identity source Trust boundary
header the X-Semantic-User and X-Semantic-Role headers safe only behind a proxy that authenticates the caller and sets the headers
jwt a bearer token validated against the issuer’s JWKS self-contained, ignores inbound identity headers

An unverified caller gets 401, a verified caller that policy rejects gets 403, and an external provider that fails or is unavailable gets 503 without falling back to allow.

How the query then authenticates to the engine is covered in Identity and the engine. Claim mapping, accepted algorithms, and the header-mode install guard are in Configuration and deployment.

Listing models, metrics, and dimensions runs through the same authentication and policy as querying.

  • A metric or column the caller’s roles cannot use is omitted from the listing, rather than shown and refused later, so a listing never reveals the shape of the warehouse.
  • Raw metric expressions are not returned unless you opt in, since the name, description, and synonyms are enough for an agent to ground on.
  • Discovery uses the built-in role policy only. External authorization covers query and SQL-plan requests, so a provider is not a way to hide names during discovery.

Metric and dimension names are resolved against the compiled model, and the request is authorized, all before any SQL string exists. Two checks apply, and both must pass. When a model names an external provider, that provider is consulted first, after request limits and before any cache lookup. The built-in role policy then runs as the statement is compiled.

The role policy is the gate that always applies. It checks three things:

  • the requested metrics against the role’s allowlist,
  • the requested dimensions and filters against the role’s denied fields,
  • every field inside every requested metric expression.

The last check is what stops a caller reading a forbidden column through an aggregate. Because it runs while the statement is built, an unauthorized request fails to compile. There is no filtered result set and no post-processing step that could be skipped.

Roles do not pool their permissions. One role must permit the whole request on its own, every metric and every field together. This differs from additive Kubernetes RBAC. If grants combined across roles, one role’s denied fields and row filters would be cancelled by another role that lacks them, and a caller holding both would see more than either role allows.

A model can also name an external provider, OPA or Ranger. It adds a second gate and never replaces the built-in policy. Because the provider is consulted before any cache lookup, a cached plan or result cannot bypass its decision, and an unavailable or unclear provider fails closed rather than falling back to allow. The provider evaluates the request and identity, and never receives SQL or raw metric expressions.

Provider result Outcome
Allow, with no obligations The request proceeds
Deny 403
Timeout, unreachable, malformed, or any obligation such as a mask or row filter 503, never a fallback to allow

Configuring a provider, and the exact input it receives, is covered in Authorization. The decision also becomes part of the cache key, described in Configuration and deployment.

The planner turns the authorized request into one SQL statement in three conceptual moves.

  • Join planning. It picks the smallest join tree that connects the datasets the request needs, so a query touching only a dimension table does not detour through the fact table.
  • Fan-out safety. If a ratio metric’s denominator aggregates a dimension across a join, summing it directly would multiply it by the number of fact rows. The planner splits those into separate aggregations and deduplicates on the dimension’s primary key.
  • Combining split aggregations. The planner stacks the separate aggregations with UNION ALL and groups the stacked rows by the requested dimensions. The engine computes each aggregation once.
  • Missing groups. Before metric filters apply, the result has every group that any aggregation in the request has. A split ratio cannot remove a group that another metric returns. When one side of a ratio has no rows for a group, the ratio is NULL for that group.
  • Zero denominators. A ratio whose denominator is zero is NULL, because the denominator is always wrapped in NULLIF(denominator, 0).
  • Governance in SQL. The role’s row filters are compiled into the statement, conjoined within a role and unioned across the roles that authorized the request. A predicate may interpolate a caller claim to express a rule like “each user sees their own region.” Ranking and the row limit are applied last.

Everything is rendered through the engine’s dialect, so identifier quoting and literal syntax match the target. Row-filter authoring is covered in Authoring a model.

The server runs one statement and returns the rows, the exact SQL, the model version, and a request hash. Every statement begins with a comment holding those same three values, so a query in the engine’s log traces back to the request that caused it.

The same request, from the same identity, against the same model version, produces byte identical SQL every time. Identity includes the principal, separate groups and roles, and all configured claims. A row filter can interpolate a claim. Two callers sharing a policy label but with different tenants compile to different SQL and never share a cached plan.

The server runs in a fixed-size pod, so every part of a request has a limit, and a request over a limit is refused rather than trimmed. Because there is no pagination yet, a truncated result could not be told apart from a complete one. The specific limits, their defaults, and the memory budget that ties concurrency to result size are in Configuration and deployment.

Agents connect over MCP and call list_models, list_metrics, list_dimensions, and query_metric. list_dimensions returns only fields with an Ossie dimension declaration. The tool descriptions and the model’s own synonyms let an agent ground a user’s wording onto a certified metric. The agent never writes SQL. How an agent request becomes SQL shows the complete exchange and explains how the planner generates SQL without an LLM.

Applications use REST. POST /v1/models/{model}/query runs a request. POST /v1/models/{model}/sql returns the plan without executing it, which is useful in review and in tests.

BI tools read the governed views directly from the engine over its native protocol.

All three go through the same compiler, so they cannot disagree about what a metric means.

An optional Valkey cache stores compiled plans and query results. Caching is an optimisation and never a correctness dependency, so a cache miss or an unreachable cache degrades to recompute and rerun. Cache keys include the full identity and the external authorization decision, so no entry crosses an identity or survives a revoked decision. The exact key composition is in Configuration and deployment.

Target Access Why
Engine model tables SELECT Executes the compiled statement
configmaps get, list, watch An informer keeps compiled models in memory
Valkey Optional Plan and result caches
JWKS endpoint Read, in jwt mode Validate bearer tokens
OPA or Ranger PDP endpoint Read, when a model names that provider Authorize query and SQL-plan requests

It needs no DDL of any kind, no access to SemanticModel resources, and no catalog or cloud credentials.

The server is stateless, so replicas are interchangeable and you scale horizontally on request rate. Determinism makes the caches effective, so repeated questions can skip the engine entirely. A header-mode deployment must sit behind something that authenticates callers. Service exposure and the chart’s safeguards are in Configuration and deployment.

Access and credentials sets out the full grant matrix for all three components.