Versioning Business Rules Like Code

Code has git. Infrastructure has Terraform state. Database schemas have migrations.

But business rules? Most teams manage them through Jira tickets and hope.

The Problem with Unversioned Rules

When a rule changes in production and something breaks, you need answers:

  • What changed? — Which rule was modified, and how?
  • When? — At what exact moment did the change go live?
  • Who? — Who authored and approved the change?
  • Why? — What was the business reason?
  • How do I undo it? — Can I revert to the previous version instantly?

If your rules live in if/else blocks, you can answer these through git history. But that means every rule change requires a code change, a pull request, a review, and a deploy.

If your rules live in a database config table, you probably can't answer any of them.

MVCC for Business Rules

TIATON uses MVCC (Multi-Version Concurrency Control) — the same pattern that PostgreSQL uses for row versioning and git uses for commits.

The data model:

-- Artifacts are identities (like git refs)
CREATE TABLE artifacts (
    pk          BIGSERIAL PRIMARY KEY,
    domain_id   TEXT NOT NULL,
    key         TEXT NOT NULL,
    kind        TEXT NOT NULL,          -- 'dmn_table', 'workflow', 'policy'
    status      TEXT DEFAULT 'active',
    head_rev_pk BIGINT REFERENCES artifact_revisions(pk),
    UNIQUE (domain_id, key)
);

-- Revisions are immutable content snapshots (like git blobs)
CREATE TABLE artifact_revisions (
    pk          BIGSERIAL PRIMARY KEY,
    artifact_pk BIGINT REFERENCES artifacts(pk),
    content     JSONB NOT NULL,
    checksum    TEXT NOT NULL,
    created_at  TIMESTAMPTZ DEFAULT now(),
    created_by  TEXT
);

-- Releases are snapshots (like git tags)
CREATE TABLE releases (
    pk              BIGSERIAL PRIMARY KEY,
    domain_id       TEXT NOT NULL,
    tag             TEXT NOT NULL,          -- 'v1.0.0', 'v1.1.0'
    base_release_pk BIGINT REFERENCES releases(pk),
    status          TEXT DEFAULT 'pending',
    UNIQUE (domain_id, tag)
);

-- Release overrides store only the delta (like git tree diffs)
CREATE TABLE release_overrides (
    release_pk  BIGINT REFERENCES releases(pk),
    artifact_pk BIGINT REFERENCES artifacts(pk),
    rev_pk      BIGINT REFERENCES artifact_revisions(pk),
    PRIMARY KEY (release_pk, artifact_pk)
);

Key properties:

  • Revisions are immutable — once created, content never changes
  • Artifacts point to headhead_rev_pk is the latest version
  • Releases are snapshots — they capture a point-in-time state
  • Delta storage — releases only store what changed from their base

Release Pipeline

A release goes through a deterministic pipeline:

pending → compiled → indexed → published
              ↓          ↓
           failed     failed

Each stage is a discrete, retriable step:

  1. Pending — Release created, snapshot resolved (recursive CTE walks base chain)
  2. Compiled — Bundle built with all artifact revisions
  3. Indexed — Content indexed for full-text and vector search
  4. Published — Live and serving traffic

If any stage fails, the release stays at its current status with error details. You can retry from the failed stage without re-running earlier stages.

Snapshot Resolution

When you create release v1.1.0 based on v1.0.0, you only specify what changed. The runtime resolves the full snapshot:

v1.0.0 (base):
  loan_eligibility  → rev_12
  risk_scoring      → rev_8
  approval_routing  → rev_15

v1.1.0 (overrides):
  risk_scoring      → rev_22  (updated)

v1.1.0 (resolved):
  loan_eligibility  → rev_12  (inherited)
  risk_scoring      → rev_22  (overridden)
  approval_routing  → rev_15  (inherited)

This is efficient — a release with 200 artifacts but only 3 changes stores exactly 3 override records.

Rollback in Seconds

Rolling back from v1.1.0 to v1.0.0 is a metadata operation. No recompilation, no redeployment. The runtime switches which snapshot it serves.

# Current production: v1.1.0
# Rollback to v1.0.0
curl -X POST /v1/admin/releases/rollback \
  -d '{"domain": "lending", "target_tag": "v1.0.0"}'

# Time: ~200ms
# Zero downtime
# Full audit trail of the rollback event

Compare this to rolling back a code change: revert commit → PR → review → CI → deploy → verify. That's 30 minutes on a good day.

Diff Between Versions

Because every revision stores content and a checksum, you can diff any two versions:

{
  "from": "v1.0.0",
  "to": "v1.1.0",
  "changes": [
    {
      "artifact": "risk_scoring",
      "type": "modified",
      "from_rev": 8,
      "to_rev": 22,
      "diff": {
        "rules_added": 2,
        "rules_modified": 1,
        "rules_removed": 0
      }
    }
  ]
}

An auditor can see exactly what changed between any two releases — down to individual rules within a table.

The Lifecycle

Every business rule artifact follows the same lifecycle:

  1. Author — Create or modify in the management UI or API
  2. Test — Run test cases against the new version
  3. Review — Business stakeholder reviews the change
  4. Release — Create a tagged release with the change
  5. Deploy — Publish the release (automatic pipeline)
  6. Observe — Monitor decisions in production
  7. Rollback — Instantly revert if something's wrong

Every step is tracked. Every version is preserved. Nothing is lost.