Skip to main content

Schema diagrams β€” auto-generated, CI-gated against drift

:::note Generated from reality, never hand-drawn None of these diagrams were drawn by hand. Each is produced by tbls reading a real schema β€” either built from the repo's migrations in CI (the fleet index below), or reverse-engineered from a live DB for adopted/vendor schemas. The example below was read from the live mlflow PostgreSQL database (on postgresql-ai) inside the cluster using existing credentials β€” nothing was uploaded to any third-party service. Snapshot: 2026-10-06. Companion to the Data stores inventory (which lists where every database is β€” this shows what's inside them). :::

Example β€” the MLflow tracking database​

How to read it: each line is a real relationship found in the database; ||--o{ means "one has many." So an experiment has many runs; each run has many metrics, params and tags; a registered_model has many model_versions; a trace has many tags/metadata. That is exactly how MLflow stores experiment-tracking data β€” discovered automatically, not documented by hand.

Why generate diagrams this way​

Hand-drawn diagramReverse-engineered (this)
Correct the day it's drawn, then rots on the next migrationAlways matches reality β€” re-reads the live schema
Lives in someone's desktop tool, unversionedCommitted to Git, rendered on this site
Manual effort per changeRegenerated in seconds / in CI

Pilot done β€” tbls, both engines βœ…β€‹

The intro diagram above was first hand-built via SQL to prove the idea; the generation is now done properly with tbls, piloted across both database engines we run:

  • Postgres β€” mlflow (19 tables): full column-level ERD with exact FK rules (incl. ON UPDATE/DELETE CASCADE).
  • MySQL/MariaDB β€” BookStack (~40 tables): tbls produced entities with columns + types + PK markers plus the role/permission relationships, e.g.:

The reusable generator is committed at minicloud-ops/scripts/db-erd/generate-erd.sh β€” it reverse-engineers any live Postgres/MySQL DB over a kubectl port-forward (creds read from the k8s Secret at runtime, nothing uploaded). tbls also emits richer per-table Markdown pages and a tbls diff drift-check.

Live across the fleet βœ… (done 2026-10-06)​

Every DB-owning repo now carries an always-fresh ER doc committed under docs/data-model/ plus a schema-erd-drift CI job that regenerates it from the repo's migrations and fails the build if the committed doc drifted β€” so the docs can't lie. The ERDs below are generated from the migrations (the same source of truth as production), not hand-drawn. Each link opens the committed README.md, whose Mermaid ER diagram renders natively on GitHub. The standard is now enforced constitution: schema-erd.md.

This table is auto-generated by scripts/gen-erd-index.py (discovers every repo committing a docs/data-model/); a new DB repo appears here with no manual step. Do not hand-edit between the markers.

RepoMigration toolchainTablesCommitted ERD
ktayl-claimsFlyway1docs/data-model
ktayl-coreFlyway5docs/data-model
ktayl-iamTypeORM6backend/docs/data-model
ktayl-policy-servicegolang-migrate5docs/data-model
ktayl-underwritingAlembic8docs/data-model
retrieva-backendDrizzle24docs/data-model
ktayl-data-platformdbt (carve-out)β€”self-documented by committed dbt model contracts (models/**/_*.yml) + CI dbt parse β€” tbls is the wrong tool for a derived warehouse (not a migration-owned schema)

Two generation modes (both committed, both drift-gated):

  • Migration-sourced (the 6 tbls repos above β€” all but ktayl-data-platform) β€” CI applies the repo's migrations to an ephemeral Postgres and runs tbls β†’ the ERD is a pure function of the migrations. Pinned for determinism (digest-pinned tbls, postgres:17.4, x86_64 baseline, explicit -t mermaid --sort -j).
  • Live-DB (adopted/vendor schemas β€” ERPNext, GLPI, BookStack, Authentik…) β€” reverse-engineered from the running DB with minicloud-ops/scripts/db-erd/generate-erd.sh over a kubectl port-forward (creds from the k8s Secret at runtime, nothing uploaded), for schemas with no in-repo migrations.

Interactive inspection/debugging of a live DB is a separate concern β€” the locked-down SSO'd Adminer cockpit or kubectl port-forward; schema changes always go through migrations (Flyway/Alembic/golang-migrate/Drizzle/TypeORM), never a GUI.