Views
DesignWrites go through typed resource CRUD; everything read goes through a view. A view is a named query
that returns a uniform ViewResult ({columns, rows}) and executes through the scoped
Storage Gateway, so the read side is a safe backend-for-frontend that the
console, the API, and an AI agent all hit without ever touching raw tables or
writing SQL. This page is the read contract; the API is the surface that exposes
it, the UI is the renderer that consumes it, and API first
is the doctrine behind both.
Why a view layer
Section titled “Why a view layer”- A single resource reads through its typed
GET(the API standard methods). Anything richer, a cross-entity aggregate, a fleet-health grid, the cascade “why did this value win” explainer, is a view: a named query the platform ships or an operator saves, not a bespoke endpoint per page. - One shape, one renderer. Every view returns
ViewResult({columns, rows}), so one renderer per shape serves every view (UI); adding a view never adds a bespoke renderer or a raw query path. - One safety boundary. A view runs through the scoped gateway, so a caller sees only the rows in its visible set, exactly as for any read. The read side can be a public BFF precisely because no view ever runs unscoped or as raw SQL.
What a view is
Section titled “What a view is”A view carries an id, a typed params schema, the query it runs, a default / private flag, and
the official boolean:
- Default views ship with the binary (curated, PR-governed, optionally backed by a Postgres view).
They are the read surface the console’s coded pages query: the Alarms page reads the
firing-nowview, notGET /alarmsdirectly, so the read contract stays uniform and the same view backs a dashboard widget unchanged. - Private views are operator-saved structured queries (filter + order + fields + params), never raw SQL. They follow the official / private namespace shadow like the registries.
- A view is parameterized: it declares typed params bound at run time. The
API runs one at
/views/{id}:run?param=; an undeclared or missing-required param is a clean 400.
ViewResult: the uniform shape
Section titled “ViewResult: the uniform shape”ViewResult is {columns, rows}: each column carries a name and type (plus role hints a renderer maps),
and rows are the records. The shape is uniform so the renderer library is decoupled from any specific
view; a field-mapping tells a renderer which column is the value, label, time, or series key
(UI).
- Cursor-paginated like any API list (
page_token), over the already-scoped result. - Views by default, materialized only when earned: most views are live queries; a hot view becomes a materialized projection only when a read profile proves the live query too slow (the same discipline as storage).
Scope and safety
Section titled “Scope and safety”- Every view runs in the gateway’s scoped mode: the caller’s
visible_set(identity and access) filters the rows, on every view, with no per-view code. A private view an operator saves cannot widen their scope: it resolves against their visible set at run time, so a saved query is never a privilege escape. - A view is read-only by construction: it never writes and has no side effects, which is what makes exposing views broadly (to the API, an MCP tool, a shared dashboard) safe.
- Presentation that depends on config (a severity level’s label and color) resolves client-side from the config view, not baked into the result.
How views are consumed
Section titled “How views are consumed”One read contract, three consumers:
- The console renders a view through the renderer library (UI): coded pages and
dashboard widgets both bind
view ref + renderer + field-mapping + params. - The API exposes every view at
/views/{id}:run(API); views are part of the public contract. - An AI agent reads through view-backed tools on the MCP surface (the agent’s search and query tools are views), scoped and audited like any caller. The read side is one contract whether a human, a script, or an agent asks.
Live updates
Section titled “Live updates”A view read is query-polling by default (a refetch interval; slow-changing config uses a long stale time). A view may stream over a server-side SSE relay where latency or fan-out earns it, the same earn-it-with-a-profile discipline (UI, time).
Versioning
Section titled “Versioning”A default view evolves additively within the API version (new columns, new optional params, never a removal or a meaning change); a breaking change to a shipped view is a new view. A private view is operator-owned data.
Related: API (the surface and /views/{id}:run), UI (the
renderer and the field-mapping), identity and access (the scope a view
runs in), storage (materialize-when-earned), and
API first (the doctrine).