Walkthrough: your first contract¶
One small contract, start to finish: validate it, lint it, generate test data, run it, and read the
result. The output below was captured from this exact example (lakelogic-mcp 0.1.3, seed=42).
With another seed the IDs, and how the failures split between rules, will differ.
You can ask your assistant to do all of it in one go — "Check orders.yaml, generate 200 rows of test data with seed 42, run it and explain the result" — or call the tools one at a time as below.
1. The contract¶
Save this as orders.yaml in your project:
version: 1.0.0
dataset: orders
info:
title: Orders
owner: sales-data@example.com
target_layer: silver
primary_key: [order_id]
model:
fields:
- {name: order_id, type: string, required: true}
- {name: customer_id, type: string, required: true}
- {name: status, type: string, required: true}
- {name: amount, type: double, required: true}
quality:
row_rules:
- accepted_values: {field: status, values: [placed, shipped, delivered, cancelled]}
- range: {field: amount, min: 0, max: 10000}
2. Validate — does it match the OLC model?¶
validate_contract(path="orders.yaml")
3. Lint — does it pass the lint checks?¶
lint_contract(path="orders.yaml")
{
"contract": "orders.yaml",
"clean": true,
"findings": [
{
"rule": "VOL-001",
"severity": "info",
"message": "silver table has no freshness or volume SLO (contract or domain) — blind to stalled feeds / missing data.",
"fix": "Add contract `service_levels.freshness`/`row_count`, or an `slo.*` block at the domain."
}
]
}
clean is true because the only finding is info: worth knowing, not a problem. explain_rule(code="VOL-001")
says why the rule exists and shows a fixed contract.
4. Generate test data¶
generate_test_data(path="orders.yaml", rows=200, invalid_ratio=0.1, seed=42)
{
"contract": "orders.yaml",
"data_file": "test_data/orders.csv",
"rows": 200,
"columns": ["order_id", "customer_id", "status", "amount", "_is_invalid", "_test_case_types"],
"intended_invalid_rows": 20
}
invalid_ratio is a request: 20 of the 200 rows were made to break a rule. The two extra columns
mark which rows, and how they were broken; they are test-data markers, not part of your contract.
5. Run it¶
run_contract(path="orders.yaml", source="test_data/orders.csv", sample=2)
{
"contract": "orders.yaml",
"ran": true,
"engine": "polars",
"good_rows": 180,
"quarantined_rows": 20,
"quarantine_reasons": {
"Rule failed: status_accepted_values (\"status\" IN ('placed', 'shipped', 'delivered', 'cancelled'))": 10,
"Rule failed: status_required (\"status\" IS NOT NULL)": 5,
"Rule failed: customer_id_required (\"customer_id\" IS NOT NULL)": 4,
"Rule failed: amount_range (\"amount\" >= 0 AND \"amount\" <= 10000)": 4,
"Rule failed: order_id_required (\"order_id\" IS NOT NULL)": 2,
"Rule failed: amount_required (\"amount\" IS NOT NULL)": 2
},
"quarantined_sample": [
{
"order_id": "ORD-658766", "customer_id": null, "status": "placed", "amount": 18.43,
"_test_case_types": "NOT_NULL_VIOLATION",
"_lakelogic_errors": ["Rule failed: customer_id_required (\"customer_id\" IS NOT NULL)"]
},
{
"order_id": "ORD-556640", "customer_id": "CUST-526170", "status": "UNKNOWN_VGV", "amount": 28.19,
"_test_case_types": "ACCEPTED_VALUE_VIOLATION",
"_lakelogic_errors": ["Rule failed: status_accepted_values (\"status\" IN ('placed', 'shipped', 'delivered', 'cancelled'))"]
}
]
}
(The sample rows are shortened here; the real response carries every column.)
Reading the result¶
- 180 + 20 = 200. Every row is accounted for: none was dropped silently.
- The 20 quarantined rows are the 20 that were made bad — the rules caught each one.
- The reasons add up to 27, not 20. A row can break more than one rule (a row with no
statusfails bothstatus_requiredandstatus_accepted_values), and each rule it breaks is counted. quarantined_sampleholds up tosampleof the quarantined rows, each with the rules it broke. Accepted rows are never returned.required: trueis a rule too. The*_requiredreasons come from the fields, not thequalityblock.
Nothing was written to the contract's target. To keep the rows, run again with write_outputs=true:
they are saved under runs/ in your project.