Internal tables

July 9, 2026 ยท View on GitHub

MongrelDB Kit reserves a set of __kit_* tables that back migrations, the schema catalog, unique-key guards, row guards, and the migration lock. They are ordinary MongrelDB tables, created automatically when a database is opened or created. Where an internal table exists in more than one language implementation, its on-disk shape is stable across TypeScript, Rust, and Python.

This page is reference material. You never write these tables directly; the kit maintains them as a side effect of migrations and CRUD.

They are hidden, and the prefix is reserved

The __kit_ prefix is reserved. Application table enumeration excludes it: db.tableNames() (TypeScript) and db.table_names() (Rust) filter out every name starting with __kit_, so internal tables never appear alongside your own. In the Rust core they are assigned reserved schema IDs just below u64::MAX.

Do not name an application table with the __kit_ prefix, and do not write to these tables yourself - the kit owns their contents and their invariants.

__kit_schema_migrations

The migration history. One row per migration the runner has recorded.

ColumnTypeNotes
versionint64Primary key. The migration's version number.
nametextThe migration's human-readable name.
checksumtextContent-aware SHA-256 of the migration (see Migrations).
applied_attextISO-8601 UTC timestamp when the record was written.
kit_versiontextKit package version that applied the migration.
statustextin_progress, applied, or failed.

The runner reads this table to compute the high-water mark (max applied version, which drives idempotency) and to verify stored checksums against the supplied migrations (drift detection). The CLI's migrate status and doctor read it. The TypeScript runner moves a record through in_progress โ†’ applied/failed; the Rust runner records successful migrations as applied.

__kit_schema_catalog

A single-row snapshot of the active schema.

ColumnTypeNotes
schema_versionint64Primary key. Always 1 - the catalog holds one current snapshot.
schema_jsontextThe serialized active schema (tables, columns, constraints, indexes).
checksumtextSHA-256 of schema_json.
written_attextISO-8601 UTC timestamp of the last write.

Rewritten when a database is opened/created and at the end of each migration run. The CLI's schema print and diff read this catalog to report and compare the stored schema.

__kit_sequences

Backs named sequence allocation in the Rust storage crate and Python facade. Current TypeScript databases use MongrelDB's native table AUTO_INCREMENT counters for sequenceDefault(...) and do not create this table for new databases. Older TypeScript databases may still contain it; the TypeScript migration runner reads it once to seed native engine counters so upgraded databases do not hand out ids below a legacy high-water mark.

ColumnTypeNotes
sequence_nametextPrimary key. The sequence's name, e.g. customers_id_seq.
next_valueint64The next id to hand out.
updated_attextISO-8601 UTC timestamp of the last allocation.

Allocation is 1-based (the first id is 1, never 0, matching SQL AUTO_INCREMENT): the Rust/Python kit reads next_value, reserves a block of count ids inside a transaction, and bumps next_value by count. See Defaults & sequences.

__kit_unique_keys

One reservation row per live unique-constraint key. This is how the kit enforces unique and composite-unique constraints, which the storage engine does not enforce natively.

ColumnTypeNotes
encoded_keytextPrimary key. A versioned encoding of (constraint, values).
constraint_nametextThe unique constraint that reserved the key.
owner_tabletextThe table that owns the row holding this key.
owner_pktextThe encoded primary key of the owning row.
created_attextISO-8601 UTC timestamp of the reservation.

There is a secondary index on owner_table. Two rows that would produce the same encoded_key collide on this table's primary key, which is what rejects a duplicate. Composite primary keys also reserve a guard here under a __pk_<table> constraint name. Rows whose unique columns include a null are not reserved (nulls never collide). The add_unique migration backfills these guards for existing rows; drop_unique and drop_table delete the ones they own.

__kit_row_guards

Optimistic-concurrency guards that keep foreign keys consistent under concurrent writes.

ColumnTypeNotes
encoded_guard_keytextPrimary key. Encodes (table, primary_key) of the guarded row.
table_nametextThe guarded (parent) table.
primary_keytextThe encoded primary key of the guarded parent row.
versionint64Bumped on every touch.
updated_attextISO-8601 UTC timestamp of the last touch.

There is a secondary index on table_name. When a child row references a parent (insert, or a foreign-key backfill), the kit touches the parent's guard; a parent delete writes the same guard key. Because both paths write the guard row, a concurrent parent delete and child insert conflict at commit, so a child can never be left pointing at a just-deleted parent. The add_foreign_key migration backfills these guards; drop_table removes the ones it owns.

__kit_migration_locks

A single advisory lock so two processes do not migrate the same database at once.

ColumnTypeNotes
lock_nametextPrimary key. The kit uses one lock named default.
holdertextThe holder label (kit).
acquired_attextISO-8601 UTC timestamp when the lock was taken.
expires_attextacquired_at plus a 5-minute TTL.

The TypeScript runner inserts this row before applying migrations and deletes it afterward. A live (unexpired) lock makes a second runner raise KitMigrationError (migration lock is already held); an expired lock is reclaimed automatically, so a crashed run does not wedge the database for more than the TTL.

See also

  • Migrations - how the runner reads and writes these tables.
  • Constraints - the unique and foreign-key rules the guard tables enforce.
  • Defaults & sequences - auto-increment semantics and the TypeScript/Rust/Python implementation split.
  • CLI - check and doctor verify these tables exist.