Script icon
VerifyContract icon
OutputValues icon
If icon
SlackIncomingWebhook icon
Log icon
Schedule icon

Soda Data Contract Compliance Gate

Verify a dataset against a Soda Core 4 data contract, alert Slack on failed checks, and log the result for data platform teams.

Categories
Data

SQL review catches a schema change when someone remembers to ask for one; it does not run on a schedule, and it does not stop a row_count collapse or a missing-value regression the moment it lands. Soda Core 4 replaced ad hoc SodaCL scans with an explicit data contract, and this blueprint verifies that contract on a schedule and treats any check that fails the contract as a real governance breach, not a dashboard someone has to remember to check.

This blueprint was created by Mohit Jeswani.

How it works

  1. seed_dataset (io.kestra.plugin.scripts.python.Script) creates a fresh orders.duckdb with three clean rows, so this flow runs end to end with no external warehouse account. Swap this task for your real data source and delete it once you point verify_contract at production data.
  2. verify_contract (io.kestra.plugin.soda.VerifyContract) verifies the kestra/orders dataset against a Soda Core 4 contract: id and amount must never be missing, and row_count must stay above 2. allowFailure: true covers a verification session error, for example a bad connection or a renamed dataset, without failing the flow outright.
  3. evaluate (io.kestra.plugin.core.output.OutputValues) reads hasErrors/hasWarnings directly from VerifyContract's own output and counts individually failed checks with a jq filter.
  4. route (io.kestra.plugin.core.flow.If):
    • compliant: logs that the contract is clean.
    • alert: posts the failed check count and names to Slack.
  5. summary logs the failed check count and severity, closing the governance cycle.
  6. errors alerts Slack if the flow itself fails, for example a pip install error.

What you get

  • A governance gate built on Soda Core 4's own contract verification, not a hand-rolled SQL assertion, so adding a check is a YAML edit to the contract, not new code.
  • A session error (bad connection, missing dataset) and a real check failure both surface, with distinct severities so you know which one happened without reading logs.
  • The exact failed check names in the Slack alert, not just a pass/fail count.

Who it's for

  • Data platform and analytics engineering teams who want a schema or volume regression caught the morning it happens, not discovered downstream in a BI dashboard.
  • Teams evaluating the Soda plugin who want a worked example of VerifyContract (Soda Core 4's data-contract model) used as a standing compliance gate, distinct from this repo's existing SodaCL Scan-based blueprints.

Why orchestrate this with Kestra

Soda Core verifies a contract once, when invoked; it does not decide a cadence, compare the result against a failure tolerance, or notify anyone. Kestra keeps max_failed_checks as a flow input, turns the verification into a Slack page naming the exact checks that failed, and keeps a full execution history of every run.

Prerequisites

  • Docker (or another Kestra task runner) able to pull python:3.12-slim, since Soda Core 4 has no official image yet and this task installs it via requirements on top of the default image.
  • Network access from the worker to pip-install soda-core/soda-duckdb.
  • A Slack incoming webhook URL.

This flow's seed_dataset step makes it fully runnable locally with nothing beyond Docker; no external account is required to exercise the default example.

Secrets

  • SLACK_WEBHOOK_URL: used by alert and the errors block. In Kestra OSS, provide it as the base64-encoded environment variable SECRET_SLACK_WEBHOOK_URL.

Inputs

Input Default Purpose
max_failed_checks 0 Failed-check count above which the Slack alert fires

Outputs

  • {{ outputs.verify_contract.result }}: full per-check verification payload (contractResults[].checks[], each with name, type, column, outcome).
  • {{ outputs.verify_contract.checkCount }} / .hasFailures / .hasWarnings / .hasErrors / .exitCode: VerifyContract's own summary fields.
  • {{ outputs.evaluate.values.failed_check_count }} / .failed_check_names: how many checks failed and their names, semicolon-joined.
  • {{ outputs.evaluate.values.severity }}: OK, WARNING, CONTRACT_BREACH, or CRITICAL.

Pitfalls

  • row_count with no threshold is a metric only, it never fails on its own; the must_be_greater_than threshold used here is what turns a row-count collapse into a real failure.
  • A verification session error (hasErrors, for example a renamed dataset) and a real check failure (hasFailures) are different severities here (CRITICAL vs. possible CONTRACT_BREACH); read failed_check_names to tell a true data-quality regression from a broken connection.
  • missing: {} on a column with no rows at all passes trivially since there is nothing to be missing; pair it with the row_count threshold in this contract so an empty dataset is still caught.

Quick start

  1. Add the SLACK_WEBHOOK_URL secret.
  2. Run the flow as shipped and confirm compliant logs a clean contract.
  3. Edit seed_dataset's INSERT to fewer than 3 rows, or set amount to NULL on one row, and rerun to confirm alert fires naming the failed check.
  4. Replace seed_dataset and dataSource/contracts with your real dataset and contract, then enable the daily trigger.

How to extend

  • Add more contracts to the contracts list to verify several datasets in one run; jq already iterates contractResults[], so the evaluate step needs no change.
  • Route a CRITICAL severity (session error) to an on-call channel separately from a CONTRACT_BREACH (real check failure), since the two have different owners.
  • Chain a downstream task that only runs when severity is OK, turning this gate into a pre-publish check before a dataset is marked ready for consumption.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.