Skip to main content

Dataset revisions

Erdo stores a file dataset’s contents as a file. Every write that replaces those contents — a refresh, a re-import, an upload that overwrites — stores the new file alongside the one it replaced rather than on top of it, so the previous version is still there after the new one goes live. A revision is one of those stored versions. This matters on the day a refresh goes wrong. If a source briefly returned fewer rows, or a rebuild ran in replace mode when it should have merged, the live dataset now holds less than it did an hour ago — and the hour-ago version is sitting in storage, intact. Revisions are how you look at it. Revisions are read-only. Nothing here rolls a dataset back, and there is no “restore” button: you read the rows out of the last-good revision and write them in again through the normal write path, so the write carries provenance, honours the dataset’s upsert key, and merges rather than clobbering whatever arrived since.

What a revision is, and is not

A revision exists for each version of a file dataset’s stored contents. Alongside them you may also see a file that was uploaded into an existing dataset and merged into it — it is stored in its own right, so it is listed and queryable, but it was never the dataset’s contents. That distinction is visible: a version that was live and has since been replaced reports when it was replaced; one that was never live reports nothing. Row-level writes — appending a row, updating one, deleting one — do not create a revision. They change the current contents in place. Revisions record whole-contents replacements, which is the failure they exist for. Datasets queried live at their source keep no revisions here. A dataset backed by a connected database or warehouse is read from that system on every query, so Erdo holds no copy to keep versions of, and its revision list is empty.

Listing revisions

The live version comes first, then superseded ones newest first. Each row carries the revision’s id — the handle for reading it — when it was created, when it was superseded (blank on the live one, and on a version that was never live), the stored file’s name, and its type. --json prints the raw result instead of a table.

Reading one

Pass a revision id to the normal read. The same DuckDB SQL you would run against the live table runs against that version’s contents, with the file exposed as a table named data:
So the question “which rows did the bad refresh drop?” is a query, not an archaeology project:
Reading a revision does not change the dataset. To put the rows back, take what the query returned and write it in the ordinary way — erdo_write_rows, POST /v1/datasets/:datasetSlug/rows, or just asking an agent — so the rows land with their provenance and the dataset’s key decides what is an insert and what is an update.

In chat

An agent working on a dataset can do all of this itself. Ask it what a dataset held before a refresh, or to recover rows a rebuild dropped, and it will list the revisions, query the last-good one, and write the missing rows back.

MCP tools

REST

Both reads need the same view access as reading the dataset itself — a revision is that dataset’s data, gated the same way. A scoped key with the datasets:query capability for the dataset can make both calls. A revision id that does not belong to the dataset you name is rejected as not found, so an id alone never reaches another dataset’s contents.