Validation results
A validation result in PLACEHOLDER Cloud is what a checkpoint run produces — one row per (asset, rule-suite) pair inside the checkpoint. It records whether every expectation passed, the failing rows (when available), and metadata about the run.
Data model
ValidationResult lives in the per-tenant schema.
| Column | What it stores |
|---|---|
checkpoint_id | FK to the executed checkpoint. |
rule_suite_id | FK to the suite that was applied. |
result | A JSONB blob — per-expectation outcome (success bool, observed value, counts), partial sample of failing rows, the rendered GX result envelope. |
success | true iff every expectation in the suite passed. |
run_time | When the validation actually executed. |
created_at | When the row was written. |
The result JSON is the source of truth for what to render in the UI. Some fields you'll find inside it:
results[i].expectation_config.kwargs— what was asserted.results[i].success— pass / fail.results[i].result.unexpected_count/unexpected_percent/partial_unexpected_list— counts and a sample of failing rows.
Operations you can do
-
List recent results — the
Resultsroute shows the most recent runs across the workspace, newest-first. A filter bar narrows the list by rule group (the post-#640 run-owning unit), pass/fail status, and a date range (#311 / #339 / #712); the filters are reflected in the URL so a narrowed view is bookmarkable and survives a refresh or a shared link, and the filter controls re-hydrate from those query params on a direct load — opening/results?rule_group=<id>lands with that rule group already selected in the dropdown, and/results?status=faillands with the Fail filter already selected (#471). 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. (An older checkpoint-era link —?checkpoint=<id>/?checkpoint_id=<id>— still parses without erroring but no longer drives the filter; the canonical UI param is?rule_group=.) Filtering happens server-side, so the "Load more" pagination walks the filtered set rather than only the rows already on screen — a rule group that runs every couple of minutes no longer 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=(start_date/end_dateare inclusive ISO-8601run_timebounds;rule_group_idreturns one rule group's parent run rollups). -
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" — with the failing count embedded for failures ("3 of 1,200 rows failed") rather than the raw Great Expectations machine name. 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 (#440). The detail page carries a Failures only toggle that hides passing checks from the list (FEATURE-20) — the count badge updates accordingly so you can tell at a glance how many failures are hidden behind the toggle.Status: partial (PARITY-6) — the rest of the drill-down UI is still in flight.
-
Custom SQL drill-down — for failing rules of type
unexpected_rows_expectation(the FEATURE-9 custom-SQL escape hatch), 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. Tracking ticket: FEATURE-21. - 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 (#441):
- 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. -
See the trend — pass-rate over the last 30 runs of a checkpoint, plotted as a sparkline.
-
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 the org owner 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. Shipped in FEATURE-29; see Publish a static data-docs site for the setup walkthrough.
Roll-ups across workspaces and teams
Two aggregator endpoints give admins a single "All" view over recent results without stitching per-scope lists together client-side (Phase-2 Unit 3 of the 3-tier tenancy work — see Multi-tenancy):
- All workspaces —
GET /api/v1/organizations/{org_id}/results/allreturns a recent-results page plus a per-workspace summary (by_workspacecount / passed / failed and an org-wideby_statussplit). It requires an org-admin-tier 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. A result's team is derived from its parent checkpoint (results carry noteam_idof their own); results from workspace-shared checkpoints — and results whose checkpoint was deleted (the run history is detached, not destroyed) — 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.
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 ?checkpoint_id= filters 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.