Engineering · Data Infrastructure

Data Versioning for Reproducibility, Auditability and Testability

In this post we will go over how we are doing data versioning and how it helps us get the auditing story right.

Ullas Sankhla Written by Ullas Sankhla LendEasy Engineering · July 2026
Workflow definition which tasks run, in what order v1 v2 v3 WE VERSION THIS Config parameters what goes into each task v4 v5 v6 WE VERSION THIS Task code the task implementation git ✓ TABLED — GIT HAS IT
Three things need versions. Task code already lives in git — this post is about the other two, and how they interplay.

The audit story we want

Our platform executes automated workflows on behalf of our clients, and every one of those runs needs to be explainable after the fact. So I want to make sure that we create an audit story consistent with our idea of being able to track every change, audit which workflow was executed, and, for each workflow that we run, capture all the configurable parameters.

To do this correctly, we need to version the workflow definition, we need to version the config parameters that go in each task and we want to version the task itself. The task is versioned for us in the git repository — so we will table that for this discussion and focus on workflow versioning and config versioning and how they interplay with each other.

Why data versioning matters

Before going further, we want to establish why data versioning is important and what it gives us. Data versioning gives us the ability to know what inputs went into which process and to reproduce any error that we have seen. Sometimes, we also want to know what our agents (or, for that matter, any business process) did — this is needed for compliance purposes so that we can give this information to auditors. Typically these needs arise in financial domains. Lastly, we can use auditability to improve testability: we can do parameter isolation (freeze all but one input), change that one, and see how our system behaves.

Reproducibility

Know what inputs went into which process; replay any error we have seen.

Auditability

Show auditors what our agents — or any business process — actually did.

Testability

Freeze all but one input, change that one, and watch how the system behaves.

Inputs workflow_def : v3 task_code : a41f2c input_data : run_118 config : v4 v5 v6 System one run per change Observed behavior only the config changed
Parameter isolation: every input frozen at a known version except one — any change in behavior is attributable to that one input.

Learnings from AWS

While working at AWS — we had very strict controls on versioning our data, code and workflow definition i.e. the process itself. I built more than one system to handle complete data versioning and auditability for large-scale data processing along with namespacing for testing — but that's a story for another day.

Here, we apply some of those learnings and build a capable but lightweight system that can achieve similar goals for a fraction of the cost.

Now that we have established that data versioning is crucial for auditability, testability and reproducibility — let's walk through how to build a system that gives us all three.

Drafts: create, test, publish

At the user level we give the user the ability to create a draft and publish the draft when they are ready or discard it when they don't need it anymore. Note that we also want the user to be able to test a draft before publishing.

Create draft Draft save × N Test Publish Discard
Drafts can be saved as many times as needed and tested before the user commits to publish — or discards.

A file-based versioning system

We have built a simple file-based data versioning system and we store the file pointers in a database. This is very basic and it lets us version each file in namespaced folders (or S3 path definitions that are specific to workflows) and simply refer to the latest file in a database table. In this table we create a column for version number and then every time the file changes we increment the version number.

workflows/collections-outreach/ cfg_9f3a.json v1 cfg_1b7c.json v2 cfg_44de.json v3 name version pointer outreach_cfg 1 2 3 cfg_… one row per file, pointer → latest files are immutable — only the pointer and version number move forward
Each save lands as a new immutable file in a namespaced folder; a database row tracks the version number and points at the latest.

Atomic publishes

To make sure everything is atomic, we create a draft copy first, and the user can save this draft as many times as they want. When the user presses publish — the draft is moved to a permanent location (move is an atomic disk operation — so it guarantees consistency when working on the local disk). And then we update the version table to point to the new file location. Here the file name needs to be unique — this can be a SHA hash if integrity is important or a UUID.

drafts/ published/ (permanent) save · save · save draft.json ab12f9.json unique name: SHA or UUID atomic move → name version pointer outreach_cfg 3 4 1b7c… ab12f9… v4 live ✓
Publish = one atomic move to a unique permanent name, then a version-table update — there is no moment where the pointer references a half-written file.

After the publish is successful, we can see the new version of the file with an incrementally updated version number.

What this leaves out

This is a bare-minimum system that then needs to be enhanced with:

  1. Data deletion — where all the data needs to be deleted,
  2. Data compaction — or garbage collection of old versions that are no longer needed

— we will skip over these concerns for now. If you are using S3 some of these come for free: S3 file versioning if you are dealing with single files, and S3 lifecycle policies for versioned data.

Testing drafts together

This covers us for having versions. Now, let's talk about testing — to cover this, we use a simple approach where we track all config and workflow drafts in a table for drafts in progress. These drafts can be viewed in their respective pages, but also can be viewed collectively so that we know what drafts (configs and workflows) are in progress at any given time. A draft workflow can refer to draft configs and they can all be tested together if desired.

Drafts in progress wf: outreach-flow draft cfg: payment-plans draft cfg: disclosures draft ▶ Test tests the whole draft set > text mode wf ✓ cfg ✓ cfg ✓ run passed simulations: coming soon
Draft workflows and the draft configs they reference are tracked together — and can be tested together before anything is published.

We have a neat test button that allows text-mode testing and soon we will also allow simulations.