← All articles

GTM Engineering

A Data Contract, Because Hoping Every System Agrees Is Not a Plan

The CROs dashboard said $14.2M, the board deck said $12.8M, and both were right. A wiki page cannot stop that. A data contract enforces one definition across enrichment, routing, and reporting: schema, validation, ownership, monitoring.

· 13 min read

The CRO’s dashboard said pipeline was $14.2M. The board deck said $12.8M. Both were pulled that morning, both from Salesforce, both by people who knew what they were doing. The gap was $1.4M and a meeting spent litigating whose number was real. It was not a data problem. It was a definitions problem: one report counted Stage 1, the other started at Stage 2, and “pipeline” had never been written down in one place both reports read from. Two correct queries of two different definitions, and a room that stopped trusting every number after.

For years the plan for this was hope. Write the definitions on a wiki page, tell everyone to use them, and hope every system agrees. Hope the enrichment tool and the routing engine and the reporting layer all mean the same thing by “segment.” They never do, because a wiki page has no version, no owner enforced by anything, no changelog, and no consumer that breaks when it drifts. Anyone edits it, nobody is notified, and every system that “implements” it drifts on its own schedule. A data contract replaces hope with enforcement. It is a spec that enrichment writes to, routing reads from, and reporting queries, all against the same schema, checked at the point of entry. The cost of skipping it is not hypothetical: Gartner puts the average price of poor data quality at $12.9M per organization per year (Gartner, 2021). The $1.4M meeting is one afternoon’s worth of that bill.

A contract is not one document you write once. It is a build you climb, four stages, each one enforcing more than the last. Watch the rungs light up before you read the argument, because the order is the whole discipline: you cannot validate a field you have not schematized, and you cannot monitor a contract nobody owns.

The data contract build, bottom to topFour stages from a shared word to an enforced interface
  1. S4Monitoring: alert when reality driftsdrift alerts

    Freshness and conformance checks watch the live data against the contract. Email decays about 22 percent a year (ZoomInfo, 2024); a monitor catches the drift and flags it before a rep calls a dead number. The contract stays true instead of true-on-launch-day.

  2. S3Ownership: a named owner and a changelogv-tagged

    Every field has an owner and every change ships as a version with a dated reason. "Qualified pipeline" moves from v2 to v3 with a line explaining why. No more silent edits that corrupt a time series nobody can trace.

  3. S2Validation: reject bad writes at the doorreject on write

    The contract checks every write against the schema before it lands. Enrichment cannot write "Enterprise " with a trailing space, routing cannot invent a sixth segment. A violation is rejected at entry, not discovered in a board meeting six weeks later.

  4. S1Schema: define the field once1 source

    Name, type, allowed values, one source of truth. "Segment" is an enum of five values from the account table, not free text three tools each guess at. Until the field has a schema, every downstream argument is an argument about what the field even is.

$1.4M
Gap from one undefined word: pipeline, in one meeting
$12.9M
Average annual cost of poor data quality per org (Gartner 2021)
4 stages
Schema, validation, ownership, monitoring: the contract build

Those three tiles reconcile to the ladder. The $1.4M is what one unschematized field cost in one room (a missing S1). The $12.9M is the annual version of the same disease across every field nobody owns. The four stages are the fix. Everything below walks the rungs in order.

Stage 1: schema, or the word is a rumor

When a backend team changes an API response shape without warning, every client breaks and everyone knows why: the interface changed and consumers were not told. When RevOps changes what “qualified pipeline” means, every dashboard, every board slide, and every rep’s mental model drift, and nobody knows why, because the change had no schema to change against. Same failure. The API team has a discipline for it; RevOps has a wiki page.

A schema is the first rung because it is the thing every other rung enforces. A field with a schema has a name, a type, a set of allowed values, and exactly one source. “Segment” is not a string that enrichment, routing, and reporting each populate their own way. It is an enum of five values, owned by the account table, and every system reads it from there. Write the contract as a spec a machine can read and a query can be generated from, not prose two people interpret two ways.

metric: qualified_pipeline
version: 3
owner: revops
status: active
description: >
  Sum of Amount for open opportunities at or past the Qualified stage,
  excluding renewals and deals with no activity in 21+ days.
grain: opportunity
source: salesforce.opportunity
filters:
  - is_closed = false
  - stage_rank >= 2          # Qualified and later; excludes Discovery
  - type != 'Renewal'
  - last_activity_date >= today - 21
measure: sum(amount)
segments: [segment, owner_id, close_quarter]
introduced: 2026-05-01
supersedes: v2               # v2 counted Discovery; see changelog

Every argument in the $1.4M meeting is settled by three lines of that file. stage_rank >= 2 says Qualified and later, not Discovery. type != 'Renewal' says new business only. The 21-day activity filter says live pipeline, not zombies. There is nothing left to interpret, because interpretation is what produced two numbers.

Stage 2: validation, or the schema is decoration

A schema nobody enforces is a suggestion. Stage 2 is where the contract grows teeth: every write is checked against the schema before it lands, and a violation is rejected at the door. This is the rung that separates a data contract from a data dictionary. dbt Labs shipped model contracts for exactly this reason, so a build fails when a column’s type or values violate the declared spec rather than silently shipping bad data downstream (dbt Labs, 2023).

The point of validation is that the three consumers of a GTM field, enrichment, routing, and reporting, write and read at different moments and cannot police each other. Enrichment writes “Enterprise ” with a trailing space at 2am. Routing reads it at 2:01am, fails to match the clean “Enterprise” enum, and drops the lead into the default queue. Reporting counts it as “unsegmented” the next morning. No single system is wrong; the contract that should have rejected the trailing space at write time was never there. Generate the query from the spec so the definition and its enforcement cannot drift:

-- generated from qualified_pipeline v3; do not hand-edit
SELECT SUM(amount) AS qualified_pipeline
FROM salesforce.opportunity
WHERE is_closed = false
  AND stage_rank >= 2
  AND type <> 'Renewal'
  AND last_activity_date >= CURRENT_DATE - INTERVAL '21 days';

Every dashboard that shows qualified pipeline calls this generated query, or reads the metric from a semantic layer that owns the spec. One definition, one query, one number, everywhere. The CRO’s dashboard and the board deck read the same file, so they cannot disagree. That is validation applied to the reporting consumer; the same spec applied at write time is validation for enrichment and routing.

Stage 3: ownership, or the change is a mystery

The reason numbers move and nobody trusts them is that the move has no owner and no explanation. Stage 3 fixes that: every field has a named owner, and every change ships as a version with a dated reason. A number that jumped between two board meetings now has a documented cause instead of a shrug.

VersionDateChangeEffect on the number
v12026-01-15Initial: all open opps, any stageBaseline, inflated by Discovery
v22026-03-01Excluded renewalsDropped ~$2.1M of renewal pipeline
v32026-05-01Started at Qualified, added 21-day activity filterDropped Discovery and zombies, ~18% lower
v4 (proposed)2026-08-01Add minimum deal size $10KRemoves long tail of micro-deals

When someone asks “why is pipeline lower than last quarter,” you do not defend the number. You point at v3, dated May 1, with the reason: we stopped counting Discovery and stalled deals. The change was a decision, documented, not a mystery. That is how you keep a room’s trust after a definition changes, and it is the rung most teams skip, because a schema and a validator feel like the finish line until the first silent edit corrupts a trend.

Stage 4: monitoring, or the contract is true only on launch day

A contract that was correct the day you shipped it rots. Data drifts even when nobody edits the schema: emails go stale, accounts get acquired, a source system quietly changes a picklist. Stage 4 watches the live data against the contract and alerts when reality drifts from the spec. Email databases decay at roughly 22 percent a year (ZoomInfo, 2024), so a “valid_email” field that passed validation in January is materially wrong by summer unless something is checking freshness. Monte Carlo built an entire category, data observability, around the fact that undetected data downtime is the expensive failure, the one you find out about from a customer or a board member rather than a monitor (Monte Carlo, 2023).

Monitoring is the top rung because it depends on all three below it. You cannot monitor conformance without a schema to conform to (S1), you cannot alert on a rejected write without validation (S2), and you cannot route a drift alert to anyone without an owner (S3). Skip a lower rung and the monitor either has nothing to check or nobody to tell.

The data contract, enforced across three consumers One spec, three systems, enforced at every write and read
qualified_pipeline v3the spec, one sourceContract layervalidates + generates queryEnrichment writeRouting readReporting query
Enrichment writes to the spec, routing reads from it, reporting queries it. Validation rejects bad writes; the owner ships versioned changes; monitoring alerts when live data drifts from the contract.

Deprecation is how a change ships without breaking history

When a definition changes, the old one does not vanish. It gets marked deprecated with a sunset date, and both versions run in parallel during the window so consumers can migrate and reconcile. This is exactly how a versioned API retires an endpoint: announce, run both, then remove. Kill a definition cold and every historical report built on it silently changes meaning overnight. Deprecate it on a schedule and the change is a migration, not a surprise. Deprecation is an ownership-stage discipline (S3) with a monitoring-stage backstop (S4): the monitor confirms nothing still reads the old version before you remove it.

The number moved because the definition did

To show why the ladder pays off, watch the same real pipeline reported under each version of the contract. The pipeline did not change. The definition did, three times, and each version tells a different story about the same underlying deals.

Same pipeline, four contract versions
Nothing about the deals changed between these bars. Only the definition did. Without a version tag, a viewer reads this as pipeline collapsing, when it is only getting honest.
View as table
ItemValue
v1 all open16.3M
v2 no renewals14.2M
v3 Qualified+live12.8M
v4 min $10K12.1M

That chart is the $1.4M meeting, resolved. The $14.2M and the $12.8M were v2 and v3 of the same metric. Neither report was wrong. Both were unversioned, stuck below Stage 3, so the gap looked like an error instead of a documented change. The tiles at the top of this piece are the same story: one undefined word cost $1.4M in a room, and the annualized version of that disease is the $12.9M Gartner number.

Wiki page vs data contract

Same definition, two ways to manage it. Only one survives a board meeting.

Wiki page Data contract
Source of truth Prose anyone can edit Versioned schema that generates the query
Bad writes Land silently, found weeks later Rejected at the door by validation
Consumers Each system reimplements it Enrichment, routing, reporting read one spec
When it changes Silently, no notice New version, changelog, sunset date
Drift over time Undetected until a customer notices Monitored; alerts on freshness and conformance
Trust in a moved number "Whose number is right?" "That is v3, here is why it dropped"
The wiki page feels lighter until the day two numbers disagree in front of the board. The contract front-loads the discipline so that day never comes.

Here’s how I’d build it: climb the four rungs

You do not need a fancy semantic layer to start. You need one file, one owner, and the discipline to route every write and read through it. Build in the order the ladder lights up, because each rung depends on the one below it.

The data contract build
  1. 1

    Schema the three fields that start fights

    Pipeline, segment, qualified lead: the ones two systems define two ways. Do not contract all forty fields. Schema the three that cost you a meeting when they disagree. Name, type, allowed values, one source.

  2. 2

    Validate at the point of entry

    Reject writes that violate the schema before they land, and generate every read query from the spec so reporting cannot hand-code its own version. A trailing space in "Enterprise " should fail at write time, not surface as an unsegmented lead.

  3. 3

    Assign an owner and version every change

    Each field gets a named owner. Any edit is a version bump with a dated reason. When a number moves, you point at the changelog instead of defending the number. This is the rung most teams skip, and the one that keeps the room.

  4. 4

    Monitor freshness and conformance

    Watch live data against the contract. Email decays ~22% a year, so a valid-on-launch field rots; a monitor flags the drift before a rep calls a dead number. Route the alert to the owner from step three.

  5. 5

    Deprecate on a sunset date, never cut cold

    When a definition changes, run old and new in parallel during a migration window so consumers reconcile. The monitor confirms nothing still reads the old version before you remove it. Cutting cold silently rewrites every historical report.

The board-ready CRM argument is that the number has to survive scrutiny. This is the layer under it: the number survives scrutiny because the field it rests on has a schema, a validator, an owner, and a monitor, so a moved number has a documented reason and a disagreement has a resolution. It is the same discipline as versioning the revenue number itself, applied one level down, to every field the number is built from. Start with one YAML file for your most-argued field and climb: schema it, validate it, own it, monitor it. Define pipeline once, enforce it everywhere, and the next time two numbers disagree you will have a changelog to point at instead of a meeting to lose.

metrics data-contract governance

Keep reading

One email. Every week.

One email a week: a system I built or broke, with the config, the numbers, and what I would change. No roundups, no theory, unsubscribe whenever it stops being useful.

The newsletter opens soon.

Connect a provider in src/config.ts