← Back to Platform Architecture
Versioning and Immutability Patterns
Overview
Stratum Platform enforces immutable versioning across precedent objects and appeals to maintain audit trails, enable rollback capability, and preserve historical context for governance and compliance. Precedent versions are snapshots, never updated in-place; appeal state transitions create audit records.
Core Principle: Snapshots, Never Overwrites
All material changes to precedents and appeals create new versioned entries in precedent_versions and appeal_timeline tables. The original record is never modified—only extended with metadata (last_modified, status changes). This pattern enables:
- Governance: Full lineage of who changed what when
- Rollback: Restore any historical version
- Compliance: Immutable audit trail for HIPAA BAA/IRB review
- Debugging: Trace decision logic across versions without guessing what changed
Precedent Versioning
Tables
CREATE TABLE precedents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
created_by UUID NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
last_modified_at TIMESTAMP NOT NULL DEFAULT NOW(),
current_version INT NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'draft', -- draft, published, archived
maturity_tier TEXT DEFAULT 'embryonic', -- embryonic, emerging, mature, canonical
win_rate DECIMAL(5, 2),
precedent_count INT DEFAULT 0,
visibility TEXT DEFAULT 'private', -- private, shared, public
-- Denormalized for fast access
payer_id UUID NOT NULL,
denial_code VARCHAR(20) NOT NULL,
lob TEXT,
state_code VARCHAR(2),
UNIQUE(tenant_id, payer_id, denial_code, lob, state_code),
FOREIGN KEY(tenant_id) REFERENCES tenants(id),
FOREIGN KEY(payer_id) REFERENCES payer_registry(id),
FOREIGN KEY(created_by) REFERENCES users(id)
);
CREATE TABLE precedent_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
precedent_id UUID NOT NULL,
version INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL,
change_type TEXT NOT NULL, -- created, updated, published, archived
change_reason TEXT,
-- Full snapshot of precedent state
title TEXT NOT NULL,
description TEXT,
denial_codes JSONB, -- array of codes in scope
payer_context JSONB, -- {payer_name, plan_types, enforcement_profile}
appeal_strategy JSONB, -- {approach, evidence_types, timeline, success_rate}
clinical_justification JSONB,
regulatory_basis JSONB,
supporting_materials JSONB, -- {research_urls, case_law, guidelines}
-- Outcome tracking
total_cases INT,
successful_cases INT,
overturn_rate DECIMAL(5, 2),
UNIQUE(precedent_id, version),
FOREIGN KEY(precedent_id) REFERENCES precedents(id) ON DELETE CASCADE,
FOREIGN KEY(created_by) REFERENCES users(id)
);
CREATE INDEX idx_precedent_versions_by_precedent_id ON precedent_versions(precedent_id, version DESC);
CREATE INDEX idx_precedent_versions_created_at ON precedent_versions(created_at DESC);
Versioning Workflow
Create — User submits precedent draft
precedents.status = 'draft', current_version = 1
precedent_versions row created: version = 1, change_type = 'created'
Update — User modifies draft (metadata, strategy, materials)
current_version increments to 2
New precedent_versions row: version = 2, change_type = 'updated'
Original precedents row unchanged except last_modified_at, current_version
Publish — Version 2 approved by compliance gate
precedents.status = 'published', maturity_tier updated to 'emerging'
precedent_versions row: version = 2, change_type = 'published'
Marketplace indexing triggered
Archive — Precedent deprecated or removed
precedents.status = 'archived', visibility = 'private'
No new version created; soft delete only
API excludes archived precedents unless explicitly queried with include_archived=true
Rollback Pattern
To restore a previous version:
// GET /precedents/{id}/versions/{versionNumber}
async function rollbackToPreviousVersion(precedentId: string, targetVersion: number) {
// Fetch target version snapshot
const targetSnapshot = await db.query(
'SELECT FROM precedent_versions WHERE precedent_id = $1 AND version = $2',
[precedentId, targetVersion]
);
// Create new version with restored state
const newVersion = await db.query(
INSERT INTO precedent_versions
(precedent_id, version, created_by, change_type, change_reason, ...)
VALUES ($1, $2, $3, 'rollback', $4, ...)
RETURNING
, [
precedentId,
currentVersion + 1,
userId,
Rollback to version ${targetVersion},
// Restore all fields from targetSnapshot
...targetSnapshot
]);
// Update precedents.current_version
await db.query(
'UPDATE precedents SET current_version = $1, last_modified_at = NOW() WHERE id = $2',
[currentVersion + 1, precedentId]
);
}
Appeal State Transitions
Appeal Timeline Table
CREATE TABLE appeal_timeline (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
appeal_id UUID NOT NULL,
state TEXT NOT NULL, -- draft, submitted, under_review, approved, denied, withdrawn, archived
state_changed_at TIMESTAMP NOT NULL DEFAULT NOW(),
changed_by UUID NOT NULL,
transition_reason TEXT,
metadata JSONB, -- {approval_notes, denial_reason, appeal_number, payer_contact}
-- Audit context
ip_address INET,
user_agent TEXT,
FOREIGN KEY(appeal_id) REFERENCES appeals(id) ON DELETE CASCADE,
FOREIGN KEY(changed_by) REFERENCES users(id)
);
CREATE INDEX idx_appeal_timeline_appeal_id ON appeal_timeline(appeal_id, state_changed_at DESC);
Appeal State Machine
┌─────────────────────────────────────────────────┐
│ DRAFT │
│ (User editing, not submitted to payer) │
│ ↓ │
├─────────────────────────────────────────────────┤
│ SUBMITTED │
│ (Appeal sent to payer, awaiting response) │
│ ↓ │
├─────────────────────────────────────────────────┤
│ UNDER_REVIEW │
│ (Payer has received and is evaluating) │
│ ↓ │
├─────────────────┬───────────────────┬───────────┤
│ APPROVED │ DENIED │ WITHDRAWN │
│ (Appeal won) │ (Appeal lost) │ (Pulled) │
│ ↓ │ ↓ │ ↓ │
├─────────────────┴───────────────────┴───────────┤
│ ARCHIVED │
│ (Outcome finalized, hidden from active list) │
└─────────────────────────────────────────────────┘
Transition Rules
| From | To | Allowed | Reason |
| DRAFT | SUBMITTED | ✓ | Appeal ready to send |
| DRAFT | WITHDRAWN | ✓ | User cancels before submission |
| SUBMITTED | UNDER_REVIEW | ✓ | Payer acknowledges receipt |
| SUBMITTED | WITHDRAWN | ✓ | User pulls before payer review |
| UNDER_REVIEW | APPROVED | ✓ | Payer approves |
| UNDER_REVIEW | DENIED | ✓ | Payer denies |
| UNDER_REVIEW | WITHDRAWN | ✓ | User pulls during review |
| APPROVED | ARCHIVED | ✓ | Outcome finalized |
| DENIED | ARCHIVED | ✓ | Outcome finalized |
| WITHDRAWN | ARCHIVED | ✓ | Cleanup |
| Any | ARCHIVED | ✓ | Admin archive |
Audit Trail Example
{
"appeal_id": "app-456",
"timeline": [
{
"state": "draft",
"state_changed_at": "2026-03-01T10:00:00Z",
"changed_by": "user-123",
"transition_reason": "Appeal created",
"metadata": {}
},
{
"state": "submitted",
"state_changed_at": "2026-03-01T14:30:00Z",
"changed_by": "user-123",
"transition_reason": "Sent to Cigna via portal",
"metadata": {
"appeal_number": "CIG-2026-0045123",
"submission_method": "portal"
}
},
{
"state": "under_review",
"state_changed_at": "2026-03-03T09:15:00Z",
"changed_by": "system",
"transition_reason": "Payer status check API",
"metadata": {
"payer_status_code": "in_progress",
"estimated_decision_date": "2026-03-10"
}
},
{
"state": "approved",
"state_changed_at": "2026-03-08T11:45:00Z",
"changed_by": "system",
"transition_reason": "Automated status sync",
"metadata": {
"approval_amount": 8500.00,
"approval_notes": "Appeal approved based on clinical evidence provided",
"payer_ref": "CIG-2026-0045123-APPROVED"
}
},
{
"state": "archived",
"state_changed_at": "2026-03-15T08:00:00Z",
"changed_by": "system",
"transition_reason": "Auto-archive 7 days after resolution",
"metadata": {}
}
]
}
Key Immutability Guarantees
No In-Place Updates — Never modify historical versions. Always create new snapshots.
Append-Only Timeline — Appeal timeline only grows; transitions cannot be deleted.
Change Attribution — Every version records who made the change and when.
Rollback Support — Any historical version is restorable as a new current version.
Audit Compliance — Complete lineage for HIPAA BAA review, IRB protocols, and regulatory audits.
Performance Implications
Indexing Strategy
-- Fast precedent history lookups
CREATE INDEX idx_precedent_versions_by_precedent ON precedent_versions(precedent_id DESC, version DESC);
CREATE INDEX idx_precedent_versions_by_user ON precedent_versions(created_by, created_at DESC);
-- Fast appeal timeline queries
CREATE INDEX idx_appeal_timeline_by_state ON appeal_timeline(appeal_id, state);
CREATE INDEX idx_appeal_timeline_by_timestamp ON appeal_timeline(state_changed_at DESC);
-- Support compliance queries: "All versions of this precedent touched by user X in date range Y"
CREATE INDEX idx_precedent_versions_by_user_date ON precedent_versions(created_by, created_at DESC);
Query Patterns
Current Version Fetch:
SELECT FROM precedent_versions
WHERE precedent_id = $1 AND version = (
SELECT current_version FROM precedents WHERE id = $1
);
Version History:
SELECT FROM precedent_versions
WHERE precedent_id = $1
ORDER BY version DESC;
Appeal History Since Date:
SELECT * FROM appeal_timeline
WHERE appeal_id = $1 AND state_changed_at > $2
ORDER BY state_changed_at ASC;
See Also
PRECEDENTS.md — Precedent object schema and queries
APPEALS.md — Appeal workflow and state machine details
AUDIT.md — Comprehensive audit logging for all actions
04-CORE-SYSTEMS/APPEALS-BUILDER.md — Appeal creation workflow
03-API-REFERENCE/ENDPOINTS-PRECEDENTS.md — Precedent versioning API endpoints
07-GOVERNANCE/COMMON-INTEGRATION-PATTERNS.md — Version history pattern implementation guide