Table of Contents

Schema History Audit Log

Every schema change ever applied to the database, permanently recorded and queryable.

Status: โœ… Implemented ยท Visibility: Public ยท Level: ๐ŸŸฃ Advanced ยท Category: Schema

๐ŸŽฏ What it solves

Schema changes in production are easy to apply and hard to reconstruct after the fact. When a field was added, when a type was widened, how many entities a migration touched, how long it took โ€” none of that is visible from the current schema alone, yet it's exactly what you need when auditing a deployment, debugging a regression that correlates with a schema change, or proving compliance with a change-management process. The audit log answers "what changed, and when" without requiring you to instrument your own deployment pipeline.

โš™๏ธ How it works (in brief)

Every time a component's schema changes on reopen โ€” a compatible evolution (fields added/removed/widened) or a breaking migration โ€” Typhon appends one entry to a built-in SchemaHistoryR1 audit trail before the new schema becomes active. Each entry captures what changed (field counts by kind), how many entities were touched, and how long it took. Entries are never updated or deleted, and a component that reopens identical to its persisted schema produces no entry. The trail is itself an ordinary system component, so it survives reopen and is queried like any other audit data.

๐Ÿ’ป Usage

// After opening the database and registering components (some of which may have evolved)
var history = dbe.GetSchemaHistory();

foreach (var entry in history)
{
    Console.WriteLine(
        $"{new DateTime(entry.Timestamp, DateTimeKind.Utc):u} " +
        $"{entry.ComponentName} R{entry.FromRevision}->R{entry.ToRevision} " +
        $"{entry.Kind} (+{entry.FieldsAdded}/-{entry.FieldsRemoved}/~{entry.FieldsTypeChanged} fields, " +
        $"{entry.EntitiesMigrated} entities, {entry.ElapsedMilliseconds}ms)");
}

Equivalent from the shell:

tsh> schema-history

  Timestamp            Component        Change             Entities   Time
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€
  2026-02-20 14:30:05  Game.Player      R1โ†’R2 compatible     15,000  0.3ms
  2026-02-22 09:15:32  Game.Inventory   R1โ†’R2 migration        8,500  12ms
Kind value Recorded when
Compatible Auto-resolved evolution โ€” fields added/removed, lossless type widenings
Migration A registered migration function ran for a breaking type change
SystemUpgrade Reserved for future internal system-schema revisions โ€” not currently emitted

โš ๏ธ Guarantees & limits

  • Append-only: entries are never modified or deleted by Typhon; GetSchemaHistory() returns the full trail in chronological (insertion) order.
  • One entry per component per reopen where a real schema difference is detected โ€” identical reopens (no field/type/index changes) write nothing.
  • EntitiesMigrated/ElapsedMilliseconds are populated only for Migration entries; Compatible entries report zero/the field-count deltas.
  • The history append happens immediately after the schema change is persisted, in the same registration call โ€” but as a separate write, not one atomic unit with it.
  • The trail itself has no built-in retention/pruning โ€” it grows for the life of the database. Plan for this if a database undergoes very frequent schema churn.

๐Ÿงช Tests

  • OperationalToolingTests โ€” SchemaHistory_RecordedOnFieldAdd (entry recorded with correct Kind/field-count deltas), SchemaHistory_SparseKeys_ReturnsAll (history readable at a high PK offset)