Skip to main content

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.

ColumnWhat it stores
checkpoint_idFK to the executed checkpoint.
rule_suite_idFK to the suite that was applied.
resultA JSONB blob — per-expectation outcome (success bool, observed value, counts), partial sample of failing rows, the rendered GX result envelope.
successtrue iff every expectation in the suite passed.
run_timeWhen the validation actually executed.
created_atWhen 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 Results route 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=fail lands with the Fail filter already selected (#471). The status param is forgiving about spelling: failed / passed are accepted as aliases of fail / 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_date are inclusive ISO-8601 run_time bounds; rule_group_id returns 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_type is 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].result as rendered_sql, unexpected_rows, and unexpected_count. Tracking ticket: FEATURE-21.

  • 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 uses element_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 result payloadunexpected_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.
  • 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 workspacesGET /api/v1/organizations/{org_id}/results/all returns a recent-results page plus a per-workspace summary (by_workspace count / passed / failed and an org-wide by_status split). 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 teamsGET /api/v1/organizations/{org_id}/workspaces/{ws_id}/results/all returns the same shape scoped to one workspace, summarised by_team. A result's team is derived from its parent checkpoint (results carry no team_id of their own); results from workspace-shared checkpoints — and results whose checkpoint was deleted (the run history is detached, not destroyed) — land in the workspace_shared bucket. Any workspace member with can_view_results may 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' plus workspace_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.