---
title: verify_dashboard
description: Prove the query service answers the plans a viewer's page will issue, before the viewer opens it. Parameters, per-plan statuses, filters, refusals.
updated: 2026-09-15
tools: [verify_dashboard]
---

Proves that a dashboard whose data Dashies keeps on its query service actually
**answers** the queries a viewer's page will issue - before the viewer opens it.

It issues **the plans the page issues**, read off the stored spec, through the
same route and the same capability a viewer gets, and reports what came back:
how many rows arrived at each grain the page asked for, how long it took, and on
any failure **the query service's own error text, verbatim**.

## Which plans it issues

- **A page whose markup you wrote** (`look`): every `dashies.data` subscription's
  grain for each dataset it asks for - its `by`, or the declared grain when that
  entry names none - folded into **one request per dataset**, exactly as the
  runtime folds them, at the default filter state the page's own `dashies.filter`
  sets before its first paint (a call outside every function, or one in a
  `dashies.data` callback guarded by an `if` that tests `page.filters` for
  absence). A call with no options asks for every dataset it may read; a call
  whose options name any dataset asks for exactly those, so a dataset every call
  leaves unnamed is asked for by nobody and gets no plan of its own.
- **A page of managed tiles** (`tiles`): each dataset's declared grain - every
  dimension live, every measure - which is the widest request a tile can make
  and stands in for the narrower ones the tiles actually ask. A `custom` tile's
  own subscriptions are added for the datasets its `reads` names.
- **A dashboard with no stored spec**: the declared grain, as above.

When no subscription asks a dataset for its declared grain, that grain is still
issued, as a separate row labelled `declared grain (no page subscription asks for
it)`, and **counted apart** in `unrequested`: a width refusal there is a fact
about the declared grain and not about the page, while a `failed` there still
names a dead object or host. A dataset **no** subscription asks for at all is
checked by that row alone, and `page.notes` names the calls that leave it
unasked.

A subscription whose options are computed rather than literal (`dashies.data(cb,
OPTS)`) cannot be read; it contributes no grain, the report says so under
`page.notes`, and only the declared-grain row checks the datasets it draws. The
same list names a `by` the dataset would refuse.

The default-filter reading is deliberately strict: a `dashies.filter` call counts
only when its path is proven, and one it cannot prove - semicolon-less code, a
pre-boot call inside an IIFE or a `DOMContentLoaded` handler, the `else` branch of
a presence test, an early-return guard, a guard inside `try`, or a guard through
an alias not named `filters` - is skipped and named under `page.notes`, and the
plans are verified at the empty state instead.

Behaviour hints for clients: read-only, not open-world. It writes nothing.

## Why this exists

`validate_cube_sql` proves the SQL runs in your warehouse. `get_refresh_status`
proves the refresh wrote rows. **Neither proves the query service can answer the
plans the page issues**, and the page cannot be opened from an agent.

That gap has already shipped a broken dashboard: a report was published, the
refresh reported success with exactly the predicted row count, and every panel
on the page read "the served query was refused (502)". Every instrument
available at the time said the dashboard was fine.

Call it after the first refresh lands, and after every republish.

## Parameters

| Parameter | Type | Required | Notes |
|---|---|---|---|
| `slug` | string | yes | Canonical slug of the dashboard to verify. |
| `filters` | object | no | A filter state to exercise, keyed by dimension: one value, or a list for a multi-select. Only dimensions a served dataset declares are accepted. It sits on top of the page's own default state, key by key. |
| `all_values` | boolean | no | Also exercise every distinct value of every declared dimension the `filters` argument does not fix, one plan per value, over the values the answered plans carried. Capped; the output says when the cap binds. |
| `workspace` | string | no | Workspace slug. Any member of that workspace may verify its dashboards. |

**Verify each value a filter offers.** A page with a region filter is not
verified by the unfiltered load alone: a filtered plan is a different query and
can fail on its own. Pass `filters: {region: "EMEA"}` for one value, a list for
several, or `all_values: true` to sweep them.

**Cells print as your page receives them.** Every cell in `first`, `total` and
the rows per grain has the type and value `dashies.data` hands your page for it:
a measure is a number when it is a safe integer, a decimal whose significant
digits survive a float64 round trip, or a `DOUBLE` result such as an `avg`, and
its exact digits as a string otherwise (a count beyond
`Number.MAX_SAFE_INTEGER`, a decimal with more significant digits than a float64
keeps); a date or timestamp measure is epoch milliseconds; a dimension is a
string; a null cell is `null`. A declared ratio is `null` wherever the page gets
`null`.

**A date dimension is swept by its day.** The query service answers a `DATE` or
`TIMESTAMP` cell as epoch milliseconds; the report prints a date dimension, and
`all_values` filters by it, as the `YYYY-MM-DD` day the page's own filter menu
offers - the value the page sends. Pass a date filter yourself in the same form:
`filters: {month: "2026-05-01"}`. On a `TIMESTAMP` dimension whose members carry
a time of day, a day literal matches only that day's midnight rows, exactly as
a viewer choosing that member sees; a swept value that answers zero rows at the
grain says so in a per-plan note rather than reading as a healthy plan.

## Returns

```text
Verified "revenue-overview": 2 of 3 plans this page issues answered. 1 did not: mix [plan=enterprise]. 1 declared-grain plan this page does not issue, counted apart: 1 refused as too wide.

Plans (the requests this page's own dashies.data subscriptions issue, every grain a dataset is asked for folded into one request as the runtime folds them, at the page's default filter state; issued one at a time):
- ok         mrr [region=EMEA]  412 ms  rows: 13 over 2 grains ([] 1; [month] 12)  first is at []  first: {month: null, region: null, mrr: 340211}  total (unfiltered): {month: null, region: null, mrr: 2841003}
- ok         mix  380 ms  rows at grain [plan]: 4  first: {plan: "pro", accounts: 118}  total (unfiltered): {plan: null, accounts: 511}
- failed     mix [plan=enterprise]  118 ms  the query host failed at span "execute". The host answered 500: {"kind":"failed","span":"execute","message":"IO Error: Connection error for HTTP HEAD to 'https://.../mix/part-0.parquet' (HTTP 404)"}
- refused    mrr [region=EMEA] declared grain (no page subscription asks for it)  9 ms  refused as too wide (query_answer_rows_exceeded): the answer holds at least 215040 cells against a bound of 110000 on node n1

Failures, in the tier's own words:
- mix [plan=enterprise]: the query host failed at span "execute". The host answered 500: {"kind":"failed","span":"execute","message":"IO Error: ... (HTTP 404)"}

Page: 2 dashies.data subscriptions read from look.html; default filter state set by the page's own dashies.filter: region=EMEA.

BEGIN_JSON
{"slug":"revenue-overview","served":true,"answered":2,"unrequested":1,"plans":4,"page":{"kind":"look","subscriptions":2,"default_filters":{"region":"EMEA"},"notes":[]},"results":[]}
END_JSON
```

The first line is the verdict to relay. It counts **the plans the page issues**;
the declared-grain rows the page does not issue get their own closing clause.
**Never report a dashboard as done on a failed plan, and relay the failure text
unchanged** - the host's sentence names the object or the limit that a summary
of it would lose.

A plan carrying several grains reports the rows at each of them (`rows: 13 over
2 grains ([] 1; [month] 12)`); a plan carrying one prints `rows at grain [plan]:
4`. The JSON carries every grain under `results[].grains` with who asked for it.
The counts satisfy
`answered + failures + reshaping + pending + unrequested + not_run === plans`.

A declared ratio measure (`ratio: { num, den }`) appears under its own key in
`first` and `total`, computed from the answered operands exactly as the page
computes it for author code, and the plan's line names it as computed, in a
legend that opens `ratio measures (computed here from the answered operands, as
the page computes them; ...)` and ends `aov_aud = revenue_aud / orders`. A plan
that answered without a declared measure names that too, after `declared
measures NOT in the answer:`, so a key your page reads that never arrives is one
read here rather than a dash on the page.

## Per-plan status

| `status` | Meaning |
|---|---|
| `ok` | The query service answered. |
| `refused` | The service declined the plan - most often a grain too wide. On a plan the page issues that is what a viewer sees; on a `declared grain (no page subscription asks for it)` row it says nothing about the page. See below. |
| `reshaping` | The dataset is being re-extracted under the spec you just published. **Not a failure, and no action is needed.** See below. |
| `pending` | The dataset is declared and its first extraction has not landed, or the workspace's query host is still starting. **Not a failure, and nothing on the dashboard needs changing.** See below. |
| `failed` | The query ran and failed. The host's own message is quoted. |
| `unavailable` | The service was not reached: no host yet, unreachable, or slower than the verification's budget. |
| `denied` | The route refused the capability. A Dashies defect, not a fact about your dashboard. |
| `not_run` | The verification's time budget ran out first. Nothing was learned about this plan. |

**A `reshaping` plan is the normal state during a republish.** The stored
objects were extracted under the previous spec, the extraction under the current
one is already dispatched, and the plan answers once it lands. It is counted
apart from both answered and failed, so a verdict where every non-answered plan
is reshaping reports no failure at all. Re-run after `get_refresh_status` shows a
newer successful run; do not retry immediately, and do not report the dashboard
broken.

**A `pending` plan is a dataset waiting on its first refresh.** The dashboard
declares it and nothing has extracted it yet, which is every warehouse
dashboard's state until its first refresh runs, so there is nothing to answer
it from. It is counted apart from answered, failed and reshaping, and the
verdict line reports it as `not yet extracted`. Unlike `reshaping`, no refresh
is necessarily on its way: ask for one with `trigger_refresh` if none is
scheduled, wait for `get_refresh_status` to report a successful run, then
verify again.

**A `pending` plan can also be the workspace's query host still starting.**
The first publish in a workspace creates the machine that answers its served
dashboards, and that machine takes about a minute or two to come up. Until it
does, every plan is reported `pending` with the per-plan text naming
`query_host_booting`, the verdict line reports it as `waiting on the query
host`, and a viewer's page paints its pending state rather than an error: a
tile reads "Updating. The first refresh will fill this in.", a metric tile's
value reads "Updating", and a page drawing its own markup gets a pending
subscription and decides for itself. That is the page's shared pending wording;
no refresh is involved here and nothing on the dashboard needs changing, so
verify again a minute later.

**A `refused` is not the same as a `failed`, and the difference decides what you
do next.** A refusal is the service declining a request as too wide. It applies
to ONE GRAIN: the service answers each grain a page asks a dataset for as its own
question, so a refused grain leaves that dataset's other grains answering, and
each grain in `grains` carries its own `refused` sentence or null. On a plan the
page issues, that is exactly what a viewer sees on the tiles drawing that grain:
subscribe at the grain you draw with `by`, bound the dimensions the dataset
declares, or declare fewer of them. On a row the page does not issue - the declared grain,
every dimension live at once, when no subscription asks for it - it is a fact
about that grain and nothing else, and the verdict counts it apart. A `failed`
or `unavailable`, by contrast, fails every narrower plan the same way, whichever
row it lands on.

The dry run already estimates this bound: a served dataset whose declared grain
can exceed it gets a `declared_grain_over_cell_bound` warning at
`/datasets/<name>` naming the estimate, the bound and the widest dimension, so
the shape is visible before the first refresh rather than after it.

## States where nothing is verified

- **Not refreshable.** The dashboard's numbers are whatever the page carries;
  there is no served path.
- **Data inside the page.** The dashboard does not use the query service, so
  there is nothing for it to answer.
- **Pending.** On the served path with no data yet: the first refresh has not
  run, so a page issues no query either. Ask for a refresh with
  `trigger_refresh`, poll `get_refresh_status` until `last_successful_run`
  moves, then verify again.

Each is reported plainly rather than as a pass. Nothing is ever faked: a plan
that was not issued says so.

## Cost

Every plan is one query on your query host, issued one at a time, and the tool
is capped so a verification cannot become the load it exists to detect. When the
cap binds the output says so and names `filters` as the way to narrow.
