Skip to content

Contracts Reference

Contracts are YAML files that define validation rules for a dataset. This doc covers contract structure and rule configuration. For rule parameters and behavior, see Rules Reference.

Structure

name: users
datasource: users.parquet

rules:
  - name: not_null
    params: { column: user_id }

  - name: unique
    params: { column: email }
Field Required Description
name Yes Contract identifier (used in history, diff). Changing this creates a new validation history -- see note below.
datasource Yes File path, URI, or named datasource
description No Human-readable description
rules Yes List of rule definitions
extends No Base contract(s) to inherit rules from

Inheritance

Inherit rules from base contracts with extends:

# base.yml
name: base_checks
rules:
  - name: not_null
    params: { column: id }
  - name: unique
    params: { column: id }
# users.yml
extends: base.yml
name: users
datasource: users.parquet
rules:
  - name: not_null
    params: { column: email }

users.yml validates with 3 rules: 2 from base + 1 of its own.

Only rules are inherited. name and datasource are not. Paths resolve relative to the child contract's directory. Multiple bases: extends: [base.yml, compliance.yml]. A base can extend another base. Duplicate rule IDs across base + child raise DuplicateRuleIdError.


Contract Identity and History

Kontra identifies a contract by a fingerprint derived from its name and rules. This fingerprint links validation runs to a contract across history and diff.

Renaming the name field is a destructive operation: it changes the fingerprint, which means all previous validation history becomes unreachable under the new name. kontra diff will show no prior runs, and kontra history starts fresh.

If you need to rename a contract, be aware that the old history remains in the state backend under the old fingerprint but will no longer be associated with the renamed contract.


Rule Definition

rules:
  - name: not_null
    params: { column: user_id }
    id: custom_id              # Optional: override auto-generated ID
    severity: blocking         # Optional: blocking | warning | info
    tally: true                # Optional: exact counts vs fail-fast
    context:                   # Optional: consumer-defined metadata
      owner: data-eng
      fix_hint: "Check ETL job"
Field Required Default Description
name Yes Rule type (see Rules Reference)
params Yes Rule parameters
id No auto Custom rule ID
severity No blocking Severity level
tally No inherit Exact counts vs fail-fast
context No Arbitrary metadata

Severity

Severity controls whether a rule failure affects result.passed.

rules:
  - name: not_null
    params: { column: user_id }
    severity: blocking         # Fails result.passed (default)

  - name: unique
    params: { column: email }
    severity: warning          # Tracked, doesn't fail result.passed

  - name: range
    params: { column: score, min: 0 }
    severity: info             # Informational only
Level result.passed Use case
blocking Affected Hard requirements
warning Not affected Monitor, don't block
info Not affected Informational metrics

Kontra records severity. The CLI maps blocking failures to a non-zero exit code.

Access in code:

result.passed              # Only considers blocking rules
result.blocking_failures   # List of failed blocking rules
result.warnings            # List of failed warning rules

Context

Attach arbitrary metadata to rules. Kontra stores it but doesn't interpret it.

rules:
  - name: not_null
    params: { column: email }
    context:
      owner: data-eng
      fix_hint: "Backfill from user_profiles table"
      pagerduty: email-quality
      tags: ["critical", "daily"]
      sla_hours: 4

Access in code:

for rule in result.blocking_failures:
    owner = rule.context.get("owner", "unknown")
    hint = rule.context.get("fix_hint", "")
    print(f"{rule.rule_id} ({owner}): {hint}")

Use cases: - Route alerts to the right team - Provide fix hints to agents - Tag rules for dashboards - Track SLAs


Tally

Controls whether rules count all violations or stop at the first.

rules:
  # Inherit from global setting (default)
  - name: not_null
    params: { column: user_id }

  # Force exact count for this rule
  - name: unique
    params: { column: email }
    tally: true

  # Force fail-fast for this rule
  - name: range
    params: { column: age, min: 0 }
    tally: false
Setting failed_count Speed
tally: true Exact Scans all rows
tally: false 1 (means ≥1) Stops at first violation

Precedence: CLI --tally/--no-tally > per-rule tally > API tally= > default (false)

Notes: - tally applies to row-level column and cross-column rules. min_rows and max_rows return an exact deficit/excess; custom_sql_check counts returned violation rows; freshness is binary and reports 1 when stale. - tally: true disables preplan for that rule (exact counts require scanning).


Custom Rule IDs

Rule IDs are auto-generated as COL:{column}:{rule_name} or DATASET:{rule_name}.

When you have multiple rules with the same auto-generated ID, add explicit id:

rules:
  # These would both be COL:shipping_date:conditional_not_null
  - name: conditional_not_null
    id: shipped_needs_date
    params: { column: shipping_date, when: "status == 'shipped'" }

  - name: conditional_not_null
    id: delivered_needs_date
    params: { column: shipping_date, when: "status == 'delivered'" }

Kontra raises DuplicateRuleIdError if IDs collide.


Severity Weights (Optional)

Severity weights are optional numeric weights for each severity level. Kontra carries them but never acts on them.

Configure in .kontra/config.yml:

severity_weights:
  blocking: 1.0
  warning: 0.5
  info: 0.1

When configured, each RuleResult includes its weight:

for rule in result.rules:
    print(f"{rule.rule_id}: weight={rule.severity_weight}")

RuleResult.to_llm() includes the weight:

COL:email:not_null: FAIL (≥10 failures)[w=1.0]

Quality Score (Optional)

When severity weights are configured, ValidationResult computes a quality score:

result.quality_score  # Float 0.0-1.0, or None if weights not configured

Formula:

quality_score = 1.0 - weighted_violation_rate
weighted_violation_rate = Σ(failed_count × weight) / (total_rows × Σ(weights))

Example: - 100 rows, 3 rules - Rule 1: blocking (w=1.0), 10 failures - Rule 2: warning (w=0.5), 4 failures - Rule 3: blocking (w=1.0), 0 failures - Weighted violations: 10×1.0 + 4×0.5 + 0×1.0 = 12 - Max possible: 100 × (1.0 + 0.5 + 1.0) = 250 - Quality score: 1.0 - 12/250 = 0.952

ValidationResult.to_llm() includes the score:

VALIDATION: test FAILED (100 rows) [score=0.95]
BLOCKING: COL:email:not_null (10)
WARNING: COL:name:unique (4)
PASSED: 1 rules

Useful for dashboards, trend tracking, and agents that need a scalar summary beyond binary pass/fail.


Datasource Formats

The datasource field accepts:

Format Example
Local file users.parquet, ./data/events.csv
S3 s3://bucket/path/file.parquet
Azure ADLS abfss://container@account.dfs.core.windows.net/path
PostgreSQL postgres://user:pass@host:5432/db/schema.table
SQL Server mssql://user:pass@host:1433/db/schema.table
ClickHouse clickhouse://user:pass@host:8123/database/table
Named datasource prod_db.users (from config)

See Configuration for named datasources.