Why AI Projects Fail Before the Model Runs: The Missing Data Contract

Five promises every dataset should make before an AI agent reads it

An AI support assistant gave a customer the wrong refund amount. The model was fine. The prompt was fine. The problem was one column.

Upstream, a team changed amount from dollars to cents. Nobody told the pipeline. Every number the assistant read was now 100 times too large, and it repeated them politely.

This is the kind of failure a data contract prevents.

What a Data Contract Is

A data contract is a written promise between the people who produce a dataset and the people who use it.

It says what the data looks like, what values are allowed, how fresh it will be, and who to call when it breaks.

Think of it like an API for a table. A web API has a fixed request and response shape. A contract gives a table the same kind of shape.

Five Promises a Dataset Should Make

1. Shape. The columns, their names, and their types. amount is a DECIMAL in dollars, not an integer in cents.

2. Valid values. status is one of open, closed, or refunded. amount is never negative.

3. Completeness. ticket_id is never NULL and never duplicated.

4. Freshness. New rows land within one hour. If the newest row is older than that, something is wrong.

5. Ownership. A named team owns the table and is told when a check fails.

None of this is new. What is new is that AI systems read data without a human looking at it first. A dashboard with a wrong number gets noticed. An agent with a wrong number just answers.

flowchart LR
  A[Source system] --> B[Bronze table]
  B --> C{Contract checks}
  C -- pass --> D[Silver table]
  C -- fail --> E[Quarantine table]
  D --> F[AI agent or dashboard]
  E --> G[Owner alerted]

A Simple Contract on a Delta Table

Delta tables can store some promises directly. The table itself then refuses bad writes.

CREATE TABLE IF NOT EXISTS workspace.default.support_tickets (
  ticket_id   STRING NOT NULL,
  status      STRING NOT NULL,
  amount      DECIMAL(10, 2),
  created_at  TIMESTAMP NOT NULL
);

ALTER TABLE workspace.default.support_tickets
  ADD CONSTRAINT valid_status CHECK (status IN ('open', 'closed', 'refunded'));

ALTER TABLE workspace.default.support_tickets
  ADD CONSTRAINT non_negative_amount CHECK (amount >= 0);

Now try to insert a bad row:

INSERT INTO workspace.default.support_tickets
VALUES ('T-100', 'pending', 25.00, current_timestamp());

Delta rejects the whole write and tells you which constraint failed. Bad data never reaches the table.

Freshness and duplicates need a small check of their own, because they depend on many rows, not one:

SELECT
  COUNT(*) - COUNT(DISTINCT ticket_id) AS duplicate_ids,
  timestampdiff(MINUTE, MAX(created_at), current_timestamp()) AS minutes_since_last_row
FROM workspace.default.support_tickets;

If duplicate_ids is above zero, or the minutes are above your promise, stop the pipeline before the AI layer reads it.

Why This Matters More for AI

An LLM does not know your units, your codes, or your rules. It trusts what it is given.

A contract moves that knowledge out of people's heads and into the table. The agent then reads data that has already been checked, and the failure shows up as a clear error for an engineer, not a wrong answer for a customer.

Try It in Free Edition

  1. Run the CREATE TABLE and ALTER TABLE statements above.
  2. Insert two good rows and one row with status = 'pending'.
  3. Read the error message and notice which constraint caught it.
  4. Run the freshness query and decide what your one-hour rule would look like.

It takes about ten minutes, and you will remember it the next time a column quietly changes.

Continue Learning