Failure modes¶
When dst does not return a clean answer, the response says why in one word. A
refusal, a clarification and an answer whose confidence was capped each carry
exactly one mode, chosen from the twenty names below. Six more names mark a
question that asked for no number and was answered before any binding
(Question types); they ride the same field and
are counted apart. The same name appears on
the response, the receipt, the request log, dst test and dst observe, so a
count by mode reads as a list of work: each name points at the file or the
component that fixes it.
A mode is a governed outcome, not a fault. When dst itself breaks (it wrote SQL
that does not parse, the warehouse timed out), the response is status: error
and carries no mode.
Where it appears¶
| Surface | Field |
|---|---|
| Query response | mode, next to status; a clarification also carries it as clarification.mode |
| Routed door, nothing covered | mode on the uncovered envelope (unroutable or provider_down) |
| Receipt | mode when a check capped the answer (data_gap, stale, unverifiable); signed with the rest |
| Request log | the mode column, one value per row; dst observe prints the counts as modes: |
dst test |
mode on every behavioural row; the summary prints the counts as modes:, and --json carries by_mode |
dst plan / dst apply |
each validation finding carries the mode it prevents, where it prevents one |
dst drift |
findings that change what a reader gets back carry stale; informational kinds carry none |
clarification.kind (ambiguous_term, unknown_value, unresolved_slot,
named_table) is still populated for one release and is deprecated. Read mode.
The twenty modes¶
Grouped by the stage that detects each one, the earliest deterministic point it can be
known (services/contracts/failure_modes.py).
Routing¶
| Mode | Meaning | Usually fixed in |
|---|---|---|
unroutable |
No lens the caller may use covers the question's words, or the covering lenses tie. | lens.yaml anchors, use_when, sources |
misrouted |
The lens chosen does not hold what was asked; it declined and named where to ask instead. | lens.yaml not_for, sources, scope |
Resolve¶
| Mode | Meaning | Usually fixed in |
|---|---|---|
unasked_filter |
A filter value the question never named would have been applied. | dst (the resolver) |
unbound_value |
A value the question names is not a stored value of any column the lens declares. | value_aliases, the probe dictionaries |
ambiguous_metric |
More than one metric, clock or meaning carries the asked word and the question does not settle it. | semantic/definitions/ (status: ambiguous, audiences), not_for |
not_declared |
The asked entity, metric, dimension or split does not exist in the layer, or the layer declares it not computable. | semantic/entities/, not_computable |
not_expressible |
Every piece is declared, but the answer's shape cannot be built in one query (a dimension on another entity, a share, a period through a join). | dst; until then a certified answer |
period_unreadable |
A period phrase the window parser does not read. dst asks for the window instead of guessing one. | dst (the window parser), or a bindings window from the caller |
Verify¶
| Mode | Meaning | Usually fixed in |
|---|---|---|
uncovered_word |
A word of the question maps to nothing in the answer: not the metric, a filter, a dimension, the period or a definition. | metric aliases, semantic/definitions/ |
window_misbound |
The window was read correctly and applied to the wrong clock or the wrong column type. | dst; a text-typed clock on an entity |
data_gap |
The window reaches past where the data runs; the gap is stated and confidence is capped. | probe.max_age, blueprint clocks |
empty_result |
Zero rows or a NULL figure where rows should exist; served as a finding, never as 0. On the Cube lane a zero from a filter value the dictionary holds, over a window every feed read is loaded through, is a measured 0: served with the window stated (empty_result:disclose); refused only when the value or the window cannot be verified. |
the filter value, or the dictionary |
wrong_grain |
The grouping, ranking or population is not the asked subject. | entity additivity, blueprint grain |
partial_ask |
A question with several parts would be answered for fewer than all of them. | dst; certified answers covering the parts |
scope_violation |
The SQL reads a table outside the lens or the caller's scope. | expose:, or ask the lens that holds it |
hidden_column |
The SQL reads a column the lens hides from this caller. | hide / except_groups |
external_access |
The SQL reaches outside the warehouse connection (a file, a URL, another database). | not configurable: refused on every door |
unverifiable |
A check could not run (no blueprint key, a table over the sampling budget, no embedder); served only with confidence unverified. |
blueprints, dst probe, the embedder |
Serve¶
| Mode | Meaning | Usually fixed in |
|---|---|---|
stale |
The data or the layer is older than declared, or a table a reader depends on changed since the baseline. | dst probe, dst drift --accept |
provider_down |
A provider the answer needs (typed decider, router decider, embedder, chat model, warehouse, Cube) is unavailable; the request names it. | provider configuration |
The six question types¶
Decided before routing and before any binding (services/resolve/question_type.py).
These are answers of their own kind, not refusals: dst observe prints them on a
question types: line and dst test tallies them as by_type, both apart from the
twenty above.
| Mode | The question asked for | What comes back |
|---|---|---|
definition |
what a term means, how a figure is calculated, how two terms differ | the member's description, expression, filters, aliases and values, or the definitions page; status: refused when no lens defines it (never invented) |
capability |
whether the data holds something, whether a question can be answered | yes with the member and the question to ask; no with what is declared; a not_computable entry quoted, as refused |
sql_request |
the SQL behind a figure | the data question served as usual, the compiled SQL (and the Cube query) as the answer body |
hand_off |
which report or place shows something | the project's hand_off: entries; refused when none is declared; "which lens holds X" names the lens and member |
admin |
the caller's own earlier requests | the caller's rows from the request log, never another caller's |
not_a_data_question |
nothing about data: an errand, a greeting, chit-chat | status: refused, naming the hand-off |
Apply and providers¶
dst apply demands only the providers a lens's lanes use. A lens with
untyped_fallback: false and no model: pin applies on an install whose only
provider is a typed-decision provider (typesafe or openai-decisions). Its
structured metrics door serves rows; the question door needs a chat model to write
the answer and returns 503 until one is configured. dst plan says so as a
lens_model_unservable warning. A lens served through Cube (serving: cube, or a
Cube-backed lens with no serving:) needs no chat model to apply either: an agent in driver mode (every MCP caller) writes its own Cube query and
dst calls no model for it, so a declared provider that cannot chat (embeddings only, or
no key) is the same warning, saying the HTTP question door without a driver returns
503. A lens that falls back to raw-SQL generation still needs its chat model, and the
same code is an error.