New to Kestra?
Use blueprints to kickstart your first workflows.
Build SQLMesh changes in dev, run data audits, then promote the exact audited build to prod. Failing audits block the release. Runs with no setup.
Ship SQLMesh changes to production only after the data audits pass. The flow clones a SQLMesh project, adds a data contract as standalone audits, builds the changes in the isolated dev environment, then audits that build. When every audit passes, it promotes the same build to prod. SQLMesh reuses the tables built in dev, so promotion is a virtual layer swap and not a rebuild. When an audit fails, production is untouched and the run ends as FAILED with the names of the failing audits.
It runs with no setup. The defaults use the public Tobiko sushi example on DuckDB, and Slack is off. To watch the gate block a release, run it with max_waiter_daily_revenue set to 500.
stage_and_audit (io.kestra.plugin.core.flow.WorkingDirectory) keeps one checkout for the next three tasks.clone (io.kestra.plugin.git.Clone) checks out the project.plan_staging (io.kestra.plugin.sqlmesh.cli.SQLMeshCLI) writes two standalone audits through inputFiles, then runs sqlmesh plan dev --auto-apply.assert_top_waiters_have_names: no row in top_waiters has a null name.assert_waiter_daily_revenue_in_range: daily revenue per waiter is between 0 and max_waiter_daily_revenue.audit runs sqlmesh audit and emits audit_status (passed or failed) plus failed_audits. It exports the project directory, including the DuckDB file that holds the data and the SQLMesh state, through outputFiles.promotion_gate (io.kestra.plugin.core.flow.If) branches on audit_status.promote_production restores the exported project with inputFiles and runs sqlmesh plan prod --auto-apply. The log shows No model batches to execute and Virtual layer updated, which confirms that the audited dev tables were promoted and nothing was rebuilt. It then prints sushisimple.top_waiters from prod.block_release (io.kestra.plugin.core.execution.Fail) ends the run as FAILED with the failing audit names.notify_slack is true, each branch also posts to Slack.on_merge (io.kestra.plugin.core.trigger.Webhook) lets CI start the gate after every merge.project_git_url (STRING, default the sushi examples repo): the SQLMesh project repository.project_branch (STRING, default main): the branch to build and promote.project_subdir (STRING, default 001_sushi/1_simple): the folder that holds config.yaml.max_waiter_daily_revenue (INT, default 1000): the upper bound used by the revenue audit.notify_slack (BOOL, default false): post the result to Slack.SQLMeshCLI uses the ghcr.io/kestra-io/sqlmesh image by default.SLACK_WEBHOOK_URL: Slack incoming webhook. Only needed when notify_slack is true.max_waiter_daily_revenue set to 500. Two days break the rule, assert_waiter_daily_revenue_in_range fails and the run ends as FAILED without touching prod.plan_staging with your own, or commit them to the project audits/ folder.key, then call /api/v1/main/executions/webhook/company.team/sqlmesh-audit-gated-promotion/<key> from CI after a merge.outputs.audit.vars.audit_status: passed or failed.outputs.audit.vars.failed_audits: comma-separated names of failing audits.outputs.promote_production.outputFiles: the promoted DuckDB file in the demo setup.The sushi example keeps its data and the SQLMesh state in a local DuckDB file, which is why the audit step exports the project and the promote step restores it. With a warehouse gateway such as Snowflake, BigQuery or Postgres, the data and state live in the warehouse, so the export is small and the same flow works unchanged. Add the warehouse credentials to the SQLMesh tasks with env and {{ secret('NAME') }}.
