No warehouse, no dbt, no Airflow to babysit. One binary runs your SQL checks with HCL assertions — once or on a schedule — speaks Prometheus, ships an MCP server, and hands back the rows that broke.
cargo install --git https://github.com/jondot/groundtruth · docker pull ghcr.io/jondot/groundtruthgt run config.hcl [PASS] orders_present 1 row(s) [FAIL] no_orphaned_line_items 3 row(s) id=3 order_id=999id=4 order_id=998id=5 order_id=997[WARN] table_not_empty[orders] 1 row(s) [PASS] table_not_empty[line_items] 1 row(s) [ERROR] deliberately_broken unknown column: recnt
Everything you need to assert your data is healthy, and nothing you don't.
Write checks in plain SQL, assert over row and rows.count. Config errors surface loud, never silently.
The validate block adds per-column rules — type, regex, ranges, uniqueness, outliers (IQR/zscore), normality.
/healthz, /metrics, /checks with health-code semantics for k8s probes and uptime monitors.
Per-check interval or cron. sustained only pages after a failure persists — no flapping.
Run gt mcp and give any agent list_checks, run_check, explain_failure. HCL reads cleanly to models.
No runtime, no agents, nothing to install. Talks to Postgres, SQLite, MySQL, and Trino from one static binary — drop it in a Dockerfile layer.
One binary replaces the warehouse-plus-scheduler-plus-exporter stack most teams bolt together.
A check is a SQL query plus an assertion. The eval context gives you row, rows.count, and each.value for fan-out.
connection "postgres" "main" { dsn = env("DATABASE_URL") } defaults { on = connection.postgres.main every = "5m" } # Liveness — page after 15m of failure check "orders_are_flowing" { query = "select count(*) as n from orders where created_at > now() - interval '5m'" fail { when = row.n == 0 sustained = "15m" } on_fail = notify.webhook.oncall } # Fan-out — one block, N checks check "table_not_empty" { for_each = ["orders", "payments"] query = "select count(*) as n from ${each.value}" warn = row.n < 1 }
It holds state and exposes health codes; your existing tooling pulls on its own schedule.
Write a check, run it once, then set it on a schedule. Everything you need is in the docs.