Declared, Enforced, Shipped
Column Mappings Validated Before Save And Visible In The Generated Package
August 26, 2026
Ask five data teams where their data contracts live and you'll get five answers. A YAML file next to the pipeline code. A wiki page. A spreadsheet named FINAL_v3. An agreement between two engineers who have since changed jobs. The contract exists. It's just not anywhere the data actually flows.
That gap is the whole problem. A contract that lives beside the pipeline can drift from the pipeline, and keeping the two in agreement becomes its own project: linters, review checklists, a build gate somebody has to maintain. We covered the patterns for surviving that arrangement in schema evolution and versioning and validation gates and enforcement. This post goes one level down, into the machinery. In BimlFlex a contract is declared as metadata, enforced before the model can even save, and shipped inside the generated artifact. There is no gap between the three, because the second and third are derived from the first.
The Declaration Is Metadata
A column-to-column mapping in BimlFlex is not a comment, a naming convention, or a row in someone's mapping workbook. It's a stored object. A Column Mapping ties a specific source object and source column to a specific target object and target column, and it's scoped to a version of the metadata model. The column itself carries the same discipline: a column knows its Target Column and its Reference Column as real relationships to other columns, not as free-text hints.
This sounds like bookkeeping until you try to answer a question with it. Which staging columns feed this hub? What breaks downstream if the source system widens this field? When the declaration is rows of structured metadata, those are queries. When it's prose in a design document, they're archaeology. It's the difference we described in data mapping strategies at scale, and it's why lineage falls out of the model for free instead of being reconstructed after the fact.
The version scope matters more than it first appears. Because every mapping belongs to a model version, a contract change is a change to a version of the model, with the old declaration still on record. The contract has history, not just a current state.
Enforcement Runs Before Save
Declaring a contract is cheap. The interesting question is what stops a broken one.
As you edit, BimlFlex validates the entire model in the background: every object, every column, every mapping, continuously, while you keep working. The rule set behind that pass is large. More than 450 distinct rules police the model, and a lot of them are aimed squarely at the mapping edges. Two examples, exactly as the product reports them:
> Multiple source columns cannot map to the same target column.
> The data types of a column and its reference column must match.
That second one is stricter than it reads. It doesn't just compare type names. It compares data type, length, precision, and scale, so a varchar(50) quietly feeding a varchar(20) is a finding, not a surprise in production three weeks later.
Findings carry a severity, from informational notes up through warnings and errors to fatal. And fatal means something concrete: while a fatal finding stands on a field in the editor, the Save button stays disabled. You can't persist the broken state. Not "the build will warn you", not "CI will catch it on merge". The model refuses to become a model that violates the contract.
Most contract tooling puts enforcement later than this. You author the contract by hand, and a check runs when someone tries to build or merge. That's real enforcement, and it's far better than nothing, but it fires after the broken change was written, committed, and pushed. Design-time refusal is a different category. The bad mapping never gets far enough to need catching, which is the point we made in design-time contracts and gates: the cheapest place to stop a data defect is before it exists.
The Contract Ships in the Package
Here's the part a validation engine alone can't give you. A contract enforced in the modeling tool still has to survive the trip into the thing that actually runs.
When BimlFlex generates an SSIS load, columns whose source and target types agree map straight across. But when the types genuinely differ, the generated data flow carries the conversion as an explicit column, and the column's name records both ends of the contract: the source name, then a __TGT__ marker, then the target column name. The shape looks like this:
<Column DataType="String" Length="100"
SourceColumn="CustomerName__TGT__CustomerFullName"
TargetColumn="CustomerFullName" />
Open the generated package in six months, long after the design meeting, and every __TGT__ column is a type boundary the model declared. A DBA who has never seen the metadata can search the artifact for __TGT__ and get an honest inventory of every place the pipeline changes a column's type. No silent casts buried in an expression. The conversion has a name, and the name is the contract.
The absence is informative too. Identical types produce no conversion column at all, so a __TGT__ column showing up in a regenerated package tells you a real type delta entered the model since the last build.
No Gap to Drift Into
Put the three surfaces side by side. The contract is declared as version-scoped metadata you can query. It's enforced by a rule engine that runs across the whole model while you edit and refuses to save a fatal violation. And it's shipped as a visible, named artifact inside the generated package.
Drift needs a gap, and there isn't one. Change a target column's type and validation re-fires against the mapping immediately. Regenerate, and the conversion column updates, appears, or disappears to match. The document-versus-system disagreement that contract processes spend so much effort policing can't open up here, because there's no document. The contract is a property of the model, and everything downstream of the model is generated from it.
That's the standard worth holding contract tooling to. Not whether a contract can be written down, but whether the running system is physically able to disagree with it.