Skip to content

The query surface (OSL-SQL)

POST /osl/query is the single consumption endpoint. Structured analytics, metrics, governed vector retrieval and graph traversal are all expressed here — you don’t learn five surfaces to read a domain.

POST /osl/query
Authorization: Bearer <jwt> # attested delegation chain, see Authentication
Content-Type: application/json

The body is one of three mutually-exclusive shapes (mixing them is 400 E0513); all three go through the same governed seam and share one request_id:

Body What it is
{ "osql": "…" } OSL-SQL — the semantic SQL dialect (the canonical surface)
{ "sql": "…" } A raw read-only SELECT over the tenant allowlist (the SQL slice)
{ "metrics": […], "group_by": […], … } MetricFlow-style metrics query (single-model simple metrics today)

osql is a semantic SQL dialect. It names only semantic objects — osl.entities.<model> and declared columns, or the table functions RETRIEVE(...) / TRAVERSE(...). It never names a physical table or a raw column.

The engine parses osql (via sqlglot, trino dialect) and lowers it to a closed operator IR — the deterministic ladder the governance model enforces over. Lowering is total and default-deny: any construct that does not lower to a known IR node is refused.

lower() classifies each query into exactly one form and validates its grammar:

SELECT segment, COUNT(*) AS n
FROM osl.entities.Customer
WHERE country IN ('ES','FR','DE')
GROUP BY segment
ORDER BY n DESC
LIMIT 100

Lowers to to_trino() → the governed SQL seam. Joins between structured entities are allowed only as a sealed-key equijoin (see below).

The only cross-relational node is an equijoin whose ON is a scalar equality on a sealed entity key on both legs — for a JointEntity leg the ON must use its declared primary_key, and the correlated facet field must be a declared, scalar-indexed metadata field. Any other JOIN … ON → 400 E0514.

There is no syntax that hands a second relational argument to a similarity or LLM operator — a relation×relation semantic join at query time (the non-goal R3) is a structural property of the grammar, not a blocklist to maintain.

X JOIN RETRIEVE(...) ON X.k = R.k is not a relational join of two physical tables. It runs as:

  1. RETRIEVE (governed) → passages, each carrying its denormalized key.
  2. Cleartext gate → the key values, or refuse (403 E0515).
  3. Structured prefilter: … WHERE k IN (<keys>) → to_trino → governed SQL (the IN-list is built with sqlglot literal conversion — injection-safe).
  4. In-cell hash join on the sealed key, projecting each leg’s columns.

In the RETRIEVE form, <op>(col) in the SELECT is a projection operator (RFC 0004). They are unary maps — never a join.

Class Operators Runs If missing
Encoder (deterministic) SIMILARITY, CLASSIFY, TAG, RANK, DEDUP, OUTLIER, CLUSTER On embeddings already in the chunk —
Decoder (inference) needs LLM EXTRACT, SUMMARIZE, SCORE, GENERATE, COMPLETE An LLM backend 503 E0521

{ "sql": "<SELECT>" } is a read-only SELECT validated against the tenant allowlist with the policy enforcement point applied (row filters + projection from the composed snapshot). { "metrics": …, "group_by": … } follows MetricFlow semantics. Both share the same governed seam as osql.

Code Meaning
200 Success
400 Malformed / out-of-grammar (E0511), non-declared object (E0512), body mixes shapes (E0513), non-sealed-key JOIN (E0514), empty sql (E0246)
403 PDP deny (E2101/E2102), cross-modal key not cleartext (E0515), object outside allowlist (E0512)
422 Valid but unresolvable — e.g. MetricFlow not yet implemented (E1001)
503 PDP snapshot missing (E2001, fail-closed), decoder op with no LLM backend (E0521)
502 Trino execution failure (E0510)

See Errors & status codes for the full envelope.