Skip to content

All postsArchitecture

Write a Data Contract With Your ERP, Even Though It Cannot Sign

A language model will read a changed export without complaint, which is exactly why it must never sit at a data boundary. The contract we enforce on every batch: shape, meaning, completeness, timing and ownership, with quarantine instead of repair.

March 18, 20255 min readWritten by Subash Natarajan

The match rate of a finance workflow fell sharply one Monday morning, and nothing had failed. No error, no alert, no stopped job. Over the weekend the ERP partner had applied an update that switched the date format in one export from day-first to month-first. Every date up to the twelfth of the month still parsed. It parsed wrongly. Invoices were being matched against the wrong week's receipts, calmly and at full speed.

The fix took an hour. Finding it took most of a day, because nothing in the design assumed an export could change its meaning without changing its shape.

Every AI workflow inside an operation depends on data nobody promised to keep stable: exports scheduled years ago, fields reused for new purposes, codes that differ by country. The ERP cannot sign a contract. We write one anyway and enforce it on every batch.

Consumer-driven contracts, as Ian Robinson described them on Martin Fowler's site in 2006, let the consumer of a service state what it relies on and have the provider test against it. A legacy ERP will never run anyone's tests. So the consumer writes the contract and enforces it alone, and the design question becomes which component is strict enough to do the enforcing.

Models are too forgiving to enforce it

Language models are good at reading messy input. A renamed column, a new date format, an extra field: the model copes. Teams take this as a feature and let the model absorb format changes.

At a data boundary it is a liability. A traditional parser that meets a changed export fails loudly, and someone looks. A model that meets the same change produces a plausible answer and moves on, and the error surfaces weeks later in a reconciliation, far from its cause. The boundary should be the least forgiving component in the system. The model receives only data that has already passed it.

A model may read a document's content. It does not get to decide what the columns of a system export mean.

What the contract covers

Most teams write down the first of these five and forget the rest.

Shape. Fields, types, formats, mandatory values. Dates in one declared format. Amounts with a declared decimal separator and sign convention.

Meaning. What a field represents, per source. Whether "posting date" is the document date or the entry date in this market. Whether credit notes arrive as negative amounts or as positive amounts with a document type. Whether a quantity is in cartons or pallets. Two exports can share a shape and differ in meaning, which is why multi-market data breaks here.

Completeness. How many records should arrive and what they should add up to. We ask the source for control totals, record counts and the sum of amounts, produced independently of the export, and compare them on every batch. A batch that is perfect in shape and missing a hundred rows is the most dangerous kind. We keep the check even for sources that "never lose rows". Every source loses rows eventually, and it happens on a day nobody is looking.

Timing. When the batch lands, which period it covers, and what happens to records posted after the cut-off. A workflow that reads an export before the warehouse posts its receipts creates exceptions that are really just lateness.

Ownership. Who on the client's side approves a change to the export, and how we hear about it. Without this, the contract is a document nobody reads until after the incident.

Failed batches are quarantined whole

The contract is code. Every incoming batch is checked against it before any business logic runs. A batch that fails is not partly processed and not repaired by guesswork, even when the fix looks obvious. Obvious fixes applied silently are how one wrong assumption becomes a month of wrong postings. The batch is quarantined with a reason, and a named person is told.

Quarantine feels slow to people who want throughput. A rejected batch costs an hour of someone's attention. A misread batch costs days of investigation and, in finance, a correction trail the auditors will ask about.

Meaning cannot always be checked mechanically, so we add sentinels: a handful of known records whose correct reading we know, checked on every run. If a known invoice from a known supplier suddenly reads as a different date or amount, the meaning has changed even though the shape has not. That Monday, a sentinel would have caught the date change in minutes.

Every source gets its own contract

When we connected four European markets for Bredent Medical, each on Dynamics 365 or a legacy system, there turned out to be no such thing as one ERP contract. Each market had configured its system differently, used its own codes and exported on its own schedule. One integration layer sat in front of all four, with a contract per source and a mapping per market into one shared model. The contracts are what kept that layer trustworthy as each market kept changing its own configuration. That matters most when a business is adding markets: every new market arrives as a new source, and it should arrive with its contract.

Bank data needs the same discipline. MT940 statements and ISO 20022 camt.053 messages carry references, value dates and booking dates that decide matching quality long before a model is involved, and banks change field usage as they migrate between formats. A contract per bank and account type, with sentinels, catches that before the reconciliation does.

Contracts are tested with broken files

Each contract ships with sample files: good ones, and deliberately broken ones with a renamed field, a changed date format, missing rows or a wrong total. The boundary must accept the first and reject every one of the second, and this runs in the test suite on every change. When the ERP partner schedules an upgrade, the client's team can pass a sample export from the test system through the boundary and know within minutes whether anything breaks.

We also agree one line with the client's IT and the ERP partner: a change to an export that feeds a workflow goes through the same change process as a change to the workflow. That line has prevented more incidents than any validation code we have written.

Loud failures are the goal

Most of what makes an AI workflow reliable in production happens before the model sees anything. A contract does not stop exports from changing. It makes the change loud, and loud failures get fixed in an hour. Silent ones get found by the auditor.

Discuss Your Operation With Our Engineers.

Describe one workflow and the systems it relies on. A senior engineer responds within two business days with an initial assessment: what we would build, what we would not, and why.

A senior engineer reads every request and replies within two business days.

Or book directly: discovery call calendar