ENGINEERING

Why our config baselines are editable and versioned - and why we didn’t use Git

When we first shipped config baselines, what you got was a photograph.

You point RFF at a golden box, it captures the effective config - registry values, screen-lock timeout, the works - and freezes it as a baseline. Then the fleet diffs against that frozen snapshot and tells you who drifted. Useful. But a photograph is a frozen moment, and the moment you want to change anything - bump screen-lock from 15 minutes to 10 - you’re stuck. Re-capture from a box you’ve manually edited? Now your “standard” is whatever happened to be on one machine at one time, and you’ve got no record of why it’s 10 instead of 15, or what it was last week.

That’s not infrastructure-as-code. That’s a screenshot of infrastructure.

Real IaC makes the desired state an editable, versioned document. You change the declared standard, the fleet converges, and every change is tracked, diffable, and revertible. So that’s what we built next: baselines you can edit by hand, with full version history. Change screen-lock 15 → 10, a new version lands, the fleet re-diffs, and if you’re enforcing, the approval-gated fixes queue up for the new value. Diff any old version against current. Don’t like it? Revert, and the fleet converges back.

The interesting part isn’t that we built versioning. Everyone wants versioning. The interesting part is what we built it with.

The reflex everyone has

Say “versioned, editable, diffable, revertible” out loud to any engineer and watch their face. They’re already typing git init in their head. Versioning is Git, as far as muscle memory is concerned.

We deliberately didn’t use Git. Not because Git is bad - I use it all day - but because every honest attempt to bolt it onto this problem fell apart for a specific reason. Three of them, actually.

Our config lives in Postgres rows, not files. A baseline is rows in a table, scoped per tenant, queried with the rest of the app. Git wants files in a working tree. To marry them you build a sync layer: serialize rows to files, commit, and on the way back parse files into rows. Congratulations, you now have two sources of truth that drift the instant they disagree about anything - whitespace, ordering, a half-finished write. If you’ve read anything else on this blog you know that “two implementations of one thing that quietly diverge” is the single most recurring bug class this project has ever produced. I am not volunteering for another one to get a feature I can build natively.

Multi-tenant Git has no clean home. Where does the repo live? If it’s our GitHub, then customer config sits in our org, which blows a hole straight through the tenant isolation (row-level security) that the entire rest of the product is built on. The whole point is that tenant A’s data is invisible to tenant B at the database level - and now I’m going to pile both of their baselines into one GitHub account I administer? No. The other option is to point at the customer’s own GitHub. Which brings us to the third problem.

Git’s concepts leak. I came up through hospital desktop engineering. I have worked alongside hundreds of excellent Windows admins, and a real number of them have never run git clone in their lives, and don’t need to. The day RFF makes someone learn what a detached HEAD is to change a screen-lock policy is the day RFF has failed them. Branches, merges, conflicts, rebases - these are wonderful and I would not give them up for my own code. But a Windows admin doesn’t want to resolve a merge conflict. They want to see:

v4 - Brian, Jun 30: screen-lock 15 → 10 min · [Revert]

That’s the whole UI. That’s the entire mental model they should ever need.

What we built instead

So: native versioning in Postgres, rendered in plain settings language, reusing the diff engine we already wrote for drift detection. Zero external dependency, RLS-scoped like every other byte of tenant data, and - worth saying plainly because it’s true - zero AI anywhere in it. It’s a couple of tables and one disciplined write path.

Here’s the model, concretely.

An append-only BaselineVersion table is the history. Every version ever written stays. Nothing in there is mutated or deleted, ever. That append-only property is, honestly, the one Git-like thing we actually wanted - an immutable record you can always walk backward through - and it turns out you get it for free from an INSERT-only table. You don’t need the rest of Git to have that.

The live baseline row stays the single “current.” This is the bit I’m happiest about. The whole fleet already diffs against one live baseline row. We did not change that. Versioning didn’t touch the convergence path at all - agents still compare against the same live row they always did. The version table sits beside it as history, not in front of it as a lookup. So we added a full audit trail and changed exactly zero lines of the hot path that every endpoint hits.

One chokepoint does all the writing. Every mutation - capture, re-capture, hand edit, revert - goes through a single function that does two things in one transaction: append a new BaselineVersion (the immutable history entry, with its human changelog line), and mirror the new state onto the live row (so the fleet picks it up). One door. You physically cannot change a baseline without writing a version, because the only code that can write the live row is the same code that writes history. That’s how you keep two things from drifting - you make it impossible to update one without the other.

Revert appends; it never rewrites. This is the subtle one people get wrong. Reverting to v2 does not delete v3 and v4 and pretend they never happened. It computes v2’s state and writes it as a brand-new v5, with a changelog line that says it’s a revert. History stays append-only and is never rewritten. You can see that someone reverted, when, and to what - which is exactly the property you wanted from version control and exactly the property a git reset --hard and force-push would have destroyed.

Stale edits get a clean 409, not a silent clobber. Two admins open v4. One saves first; now current is v5. The second one saves - and instead of quietly overwriting the first person’s change, they get a 409 Conflict. We do it with optimistic concurrency on a content hash: the edit carries the hash of the version it was based on, and if that no longer matches current, we refuse. No locks, no last-write-wins, no mystery about whose change survived. Just “this is stale, reload and try again.”

The payoff

String it together and the loop is genuinely satisfying:

  1. You edit a value - screen-lock 15 → 10.
  2. A new version lands with a human changelog line, and the new state mirrors onto the live row.
  3. The fleet re-diffs against the new current. Everyone who was at 15 is now drifted.
  4. If you’re enforcing, approval-gated fixes queue up to push the new value out.
  5. You can diff any historical version against current to see precisely what moved.
  6. Don’t like it? Revert. A new version appends, the live row updates, the fleet converges back.

No repo. No sync job. No “which branch is production.” No teaching a desktop engineer what a rebase is.

Removing a setting is not the same as deleting a value

There’s a trap hiding inside “editable desired state,” and we walked straight into it while dogfooding. You add a setting to a baseline, enforce pushes it to the fleet, and a week later you decide you don’t want to manage that setting anymore. So you delete the line from the baseline. Reasonable. And then you notice the value is still on every machine.

That feels like a bug the first time you see it. It is not. It’s the single most important safety decision in the whole feature.

Removing a setting from a baseline means “stop managing this” - not “wipe it off every box.” Those are two completely different intents, and the tools that quietly treat them as the same thing are exactly the tools that nuke fleets. Think about what the other reading actually implies: the instant you delete a line from your standard, the product reaches out and deletes that registry value from hundreds of machines, including the ones where something else set it on purpose. That’s not desired-state management. That’s a loaded gun pointed at your endpoints.

So our default is the safe one. A baseline asserts the settings it lists and ignores everything else on the machine. Drop a setting and RFF simply stops looking at it. Nothing gets deleted, because we will not remove a value we were never explicitly told to remove.

When you do want a value gone fleet-wide, you mark it not allowed. That’s a first-class declaration - “this value must not exist” - and it generates the same approval-gated, fully-undoable removal job that every other change in RFF goes through. You read the exact script before it runs. You can roll it back. It’s versioned like everything else. The destructive action exists; it’s just never the accident. You have to mean it. It’s the same instinct behind our brick-safe Secure Boot cert rollout: the dangerous, fleet-wide action has to be something you consciously reach for, never something a default does to you.

“Stop managing it” is one click. “Delete it everywhere” is a different, deliberate click. We will never make the first one silently do the second.

The lesson

“Use Git” is the reflex for versioning the way “use jQuery” used to be the reflex for a click handler. It’s not wrong, exactly - Git is a versioning system - but it smuggles in a pile of things we explicitly did not want: files (we have rows), a sync layer (we have a bug history that says no), a multi-tenancy story with no clean answer, and a UX built for people who live in a terminal.

What we actually wanted was narrower than Git: an append-only history and diffs. That’s it. And it turns out you can get both from an insert-only Postgres table plus the diff engine you already wrote - fully tenant-isolated, with none of the baggage.

Versioning is not Git. We wanted the versioning. We left the Git on the shelf.


Config baselines are part of RFF - free for your first 100 endpoints. Capture a golden box, edit the standard, watch the fleet converge - getrff.com.