Results
A result is the recorded outcome of an execution run: which rules passed, which failed, how many rows broke each one, and a sample of the failing rows where the rule type captures them. The Results page lists every run across your workspace, newest first; open one to see exactly what went wrong and jump straight back to the rule and the asset.
Each validation run produces one validation-level result (the rollup, with the run's health score) and one result per asset it covered. Results are immutable history: editing a rule later never rewrites a past result.
What a result holds
| Field | What it stores |
|---|---|
success | true only if every rule in the result passed. |
result | The per-rule outcomes — pass/fail, observed values, failing counts and percentages, and sample failing rows. |
rule_group_id / rule_group_name | The validation that ran (the API's machine name for a validation). |
data_asset_id / data_asset_name | The asset a per-asset result covers (null on the validation-level rollup). |
run_time | When the run executed. |
Inside result, each entry under results[i] carries what was asserted (expectation_config.kwargs), whether it passed (success), and its counts and samples (result.unexpected_count, unexpected_percent, partial_unexpected_list).
Operations you can do
-
List recent results — the
Resultsroute shows the most recent runs across the workspace, newest-first. Every column sorts and filters from its own header: click a header (Result, Validation, Asset, Run time) to sort by it, and click its funnel to filter — Result by pass / fail, Validation by one validation, Asset by name (contains), and Run time by a From / To day range. Active filters show as removable chips above the table, with Clear filters. Sorting and filtering both happen on the server, so they apply to every run, not just the rows loaded so far. The sort and filters are reflected in the URL (sort,rule_group,status,asset,start_date,end_date) so a narrowed view is bookmarkable and survives a refresh or a shared link, and the headers re-hydrate from those query params on a direct load — opening/results?rule_group=<id>lands with that validation already filtered, and/results?status=faillands with the Fail filter already on. The status param is forgiving about spelling:failed/passedare accepted as aliases offail/pass, and an unrecognized value simply drops to no status filter rather than erroring. Filtering happens server-side, so the "Load more" pagination walks the filtered set rather than only the rows already on screen — a validation that runs every couple of minutes never buries everything else. The same filters are available on the API:GET /api/v1/.../validation-results?rule_group_id=&checkpoint_id=&status=pass|fail&start_date=&end_date=&asset=&sort=(start_date/end_dateare inclusive ISO-8601run_timebounds;rule_group_idreturns one validation's run rollups;assetmatches the owning data asset's name, case-insensitive contains;sortis one ofrun_time,result,validation,asset, with a leading-for descending, and defaults to-run_time). Click any row to open that run; the Asset column links straight back to the data asset the run validated. A row with no asset — a validation-level rollup, or a run whose asset is in the recycle bin or hidden from you — shows a dash instead of a link. On the API every list row carries the same pair,data_asset_id/data_asset_name(bothnullwhen there's no asset you can open). -
Drill into a result — see every check that ran, with pass/fail counts and (for failing column-map rules) a sample of unexpected rows. Each row leads with a plain-English sentence describing what the check asserts — e.g. "Email is never blank" — rather than the raw Great Expectations machine name. Its result leads with the status, then the detail: "Failed · 3 of 1,200 rows failed", or "Passed · observed 5", so a pass always reads as a pass. The engine's
expectation_typeis demoted to a hover tooltip (the ⓘ marker beside the name), so the jargon is available on demand without competing with the plain language. The detail page carries a Failures only toggle that hides passing checks from the list — the count badge updates accordingly so you can tell at a glance how many failures are hidden behind the toggle. -
Jump back to the asset — and to the failing rule — the result detail header carries a link back to the data asset the run validated, so you can get from a failure straight to the asset it belongs to. This works for every run with an asset, including the per-asset runs of a validation; those also link back to their validation. Each check in the breakdown, passed or failed, opens its rule in an editor right on the result page: click the rule's name or anywhere on its row, focus the row and press Enter or Space, or use the row's pencil icon (Edit rule, named for the rule it edits). The expand arrow only shows the check's details. For a custom rule type the platform can't match to a stored rule, an arrow icon (Open rule) deep-links to the asset's Rules tab instead. When a result has no asset (a legacy run recorded without one, or a validation run's rollup), the asset link and the per-rule controls are left out rather than shown broken. Each result carries a stamp naming the rule that produced it, so this works for results from a Privacy Agent source too, whose rule arguments never leave your network. A recent result's stamp names the rule itself, so the result still opens its rule after the rule is edited. An older result is matched on its rule's settings as they were at run time (its type and arguments, for one recorded before stamps existed), so a rule edited since that run may no longer be found. Neither can a rule removed since the run. The editor says so, and the deep link still opens the asset's Rules tab without focusing a specific row.
-
Drill into a validation run — a run's page is titled Validation run with its run time (the browser tab reads Validation run · validation name), and a validation run's page lists its Per-asset results. A run that failed before it could check any rule (the data source was unreachable, or the run never started) reads "Failed before any rule was checked" with the reason, rather than "0 rules failed". Click a row, or focus it and press Enter, to open that asset's own result. The Asset column links to the data asset itself.
-
Custom SQL drill-down — for failing rules of type
unexpected_rows_expectation(a Custom SQL rule), the expanded row in the result detail shows three pieces of context:- The rendered SQL — the
{batch}-substituted query the worker actually ran, in a monospaced block with a Copy SQL button. The query is the same string you would paste into your own SQL client to reproduce the failure; the platform never re-executes it for you, so the boundary between read-only result viewing and live datasource access stays clean. - A sample of failing rows — up to 10 rows, captured at run time, rendered as a vanilla
<table>with column headers derived from the row keys. The 10-row cap keeps the persisted result JSON bounded; if your custom-SQL rule flags a wide table, the sample is enough to triage without bloating the database. - A "Showing X of Y" badge — the worker stamps
unexpected_count(the pre-cap total) on each custom-SQL result entry, so the UI can tell you "Showing 10 of 4218" when the failure set is bigger than the captured sample. If the worker didn't capture a count (older results, non-custom-SQL rules), the badge is omitted.
The values are persisted under
results[i].resultasrendered_sql,unexpected_rows, andunexpected_count. - The rendered SQL — the
-
Plain-English failure explanation — for any other failing rule (not custom SQL), the expanded row leads with a one-line conclusion, then evidence, then the raw payload last — never the other way around:
- The conclusion comes first: a plain sentence with the failing count embedded — "3 of 1,200 rows failed: email is never blank". Agent-mode results carry the evaluated total under
evaluated_count(direct-mode useselement_count), so both read "N of M rows failed" rather than just "N rows failed". This sentence is the primary, always-visible layer, so a reviewer never has to read JSON to understand what went wrong — and it works with no LLM configured (the "Explain with AI" panel is an enhancement, not the baseline). - A sample of failing rows follows, when the run captured any (the same up-to-10-row table the custom-SQL drill-down uses).
- The raw
resultpayload —unexpected_percent,partial_unexpected_index_list, and the full JSON — is tucked behind a collapsed "Technical details" disclosure. Float fields are rounded to two decimal places so the panel reads 8.56, not 8.555555555555555. The information is kept for power users and support requests; it just no longer leads.
- The conclusion comes first: a plain sentence with the failing count embedded — "3 of 1,200 rows failed: email is never blank". Agent-mode results carry the evaluated total under
-
Root cause analysis — for a failed run, the detail page surfaces a Root cause analysis card that correlates the failure with signals already in the tenant schema and ranks the likely causes. It answers why the run failed, not just which expectation broke. See Root cause analysis for what feeds it and how to read it. The data is served by
GET /api/v1/.../validation-results/{id}/rca. -
AI explanation — for a failed run, an AI explanation card sits below Root cause analysis. Both cards start collapsed, so the page leads with the status and the failing rules. Expand a card to load it. The explanation is generated only when you expand its card, so opening a result never spends an LLM call on its own. Collapsing and re-expanding within a few minutes reuses the explanation already generated. It needs a reachable AI engine; see AI features.
-
See the trend — how many rules failed in each of a validation's recent runs is charted on the validation page's Failures card (click a bar to open that run). A result's page also charts Recent runs: for a validation run's per-asset result, the same asset's earlier results in the same validation; for a legacy checkpoint result (labelled as an asset run), that checkpoint's runs. On the API,
GET /api/v1/.../validation-results?data_asset_id=<uuid>lists one asset's results. -
Comment on a result for triage context. Reviewers can record why a failure was expected ("upstream incident DATA-1234"), link to the tracking ticket, or note the workaround so the context survives the Slack thread. Comments are stored as plain text (8 KB cap), shown newest-first with the author's email, and only the author or a super admin can edit or delete an existing comment. Authored via
POST /api/v1/.../validation-results/{id}/comments; see Result comments in the API reference. -
Export to data docs — produce a static HTML site listing every recent result and publish it to S3 / GCS / a local directory. See Publish a static data-docs site for the setup walkthrough.
Results across all workspaces
Pick All workspaces in the workspace selector (super admins only) and open Results to see every workspace's runs in one place. The selector keeps the page you're on, so choosing All workspaces while on Results shows the org-wide Results; the sidebar's Dashboard and Results links keep the scope, and every other page is per-workspace. The page shows the totals (runs, passed, failed), a By workspace and a By team breakdown, and the runs themselves with Workspace and Team columns you can filter from their headers. The asset is named without a link, because the asset page opens in your active workspace; clicking a run from another workspace switches to that workspace and opens it. The Workspace and Team header filters narrow the runs loaded so far; use Load more to page further.
This replaces the separate Roll-up view. Old /results/all links open Results: ?scope=org opens it for all workspaces, and the plain link opens the active workspace's list, which already shows each run's team.
The roll-up endpoints
Two aggregator endpoints give a single "All" view over recent results without stitching per-scope lists together client-side (see Multi-tenancy for how workspaces and teams scope what you see):
- All workspaces —
GET /api/v1/organizations/{org_id}/results/allreturns a recent-results page plus an org-wide summary:by_workspaceandby_team(count / passed / failed each) and aby_statussplit, plusworkspace_names/team_namesthat name each of those keys. A deleted (archived) workspace's runs stay in this view, labelled "(archived)", but can't be opened. It requires the super admin role ( super_admin): the endpoint refuses to serve a partial "org-wide" picture to callers who can only see some workspaces. - All teams —
GET /api/v1/organizations/{org_id}/workspaces/{ws_id}/results/allreturns the same shape scoped to one workspace, summarisedby_team. This summary derives a result's team from its parent legacy checkpoint (results carry noteam_idof their own), so results with no team-scoped checkpoint — which includes every validation run, since checkpoints were retired — land in theworkspace_sharedbucket. Any workspace member withcan_view_resultsmay call it: the roll-up reflects exactly the caller's visibility, so a workspace admin sees every team's bucket while a team-tier member sees only their teams' plusworkspace_shared.
Each row carries its workspace_id and team_id. A row's team is the team of the validation that ran it, falling back to its legacy checkpoint's team; null means no team owns it. The org summary's by_team uses the same rule, with rows that have no team under workspace_shared. Rows also carry the owning asset (data_asset_id / data_asset_name, null when there's none you can open).
Both summaries are computed in SQL (GROUP BY) over the same filtered scope the data page walks, so total_count always matches what you can page through with the cursor / limit params. The summary appears on the first page only — cursor pages return summary: null, since the roll-up is identical on every page of the same walk; keep the first page's value while paginating. Optional ?since= (naive timestamps are interpreted as UTC) and the deprecated ?checkpoint_id= filter (a retired checkpoint's runs, plus the runs of the validation it maps to) narrow page and summary together. Page rows omit the full result payload — fetch a single result's detail endpoint for that. Full parameter table in the API reference.
Retention
Results stay in the per-tenant schema indefinitely today. Self-hosted operators can prune old rows directly via SQL; a retention policy in product is not yet planned.