Skip to content
Daniel Hokanson edited this page Aug 30, 2026 · 2 revisions

forge-db owns the Forge database schema. It is a version-controlled tree of desired-state SQL — one file per object, the way a SQL Server .sqlproj is laid out — plus a small .NET harness that reconciles any live PostgreSQL database to that tree using stripe/pg-schema-diff as the diff engine. Postgres has no .dacpac; this repo is Forge's answer to that gap.

Two consequences shape everything else here. forge-api ships no EF Core migrations — EF is a query-mapping layer and nothing more. And the API's SchemaBootstrapper provisions a fresh database but is a no-op on a populated one, so on any install that already holds data, the reconcile in this repo is the only thing that carries the schema forward. That single fact is behind the most common upgrade failure in Forge: an API image swapped onto a database that was never reconciled.

What lives here

Path Role
schema/ The desired state. extensions/, tables/, indexes/, views/, functions/, triggers/ — one object per file. The source of truth you edit
premigrate/ Applied-once scripts that run before the reconcile — the escape hatch for what a state diff cannot express (renames)
data/ Ordered, applied-once backfills coupled to a schema change. The diff engine emits DDL, never data
seed/ Schema-adjacent reference rows the application assumes exist
scrub/ Cleanup rules for the clean-rebuild import — where "garbage" is defined once instead of as somebody's ad-hoc DELETE
history/ Apply receipts. Output only — never edited, never replayed
src/Forge.Db The harness CLI: assemble, plan, verify, apply, dump, import, baseline
tools/apply-schema.sh Turn-key plan → apply → verify against a local stack

INPUT versus OUTPUT is load-bearing. schema/, premigrate/, data/, seed/ and scrub/ are inputs you author. history/ is a receipt of what happened. Conflating the two is the failure this layout exists to prevent.

Three pages, in reading order

  • Schema Ownership — why the schema left EF, what "desired state" means concretely, how the tree assembles into one ordered DDL file, and exactly what SchemaBootstrapper does and does not do.
  • The Reconcile Harness — the verbs, the three apply phases, the gates that stop a destructive plan, the pre-migrate sharp edge, and how a split install has to run the reconcile on the database box.
  • Dump and Import — the archive format, its interchangeability with the app's own Admin → Database export, and the clean-rebuild semantics.

Working on the schema

A self-hoster installs none of this tooling — the reconcile arrives as a container image that forge-deploy runs as a one-shot. The prerequisites below are for changing the schema or running the harness by hand.

You need the .NET SDK the harness targets (see Forge.Db.csproj), the pg-schema-diff binary on PATH or named by PG_SCHEMA_DIFF_BIN, and a pgvector-capable PostgreSQL whose connecting user holds CREATEDB — the diff engine provisions its own temporary database on the target server to compute the plan. pg-schema-diff publishes no prebuilt binaries; the pinned version and the go install line are in the README and the Dockerfile.

The loop for a schema change is short: edit the object's file in schema/ (never write an ALTER — the engine derives it), run plan against a scratch database, run apply, then regenerate the copy forge-api embeds with assemble. Details and the traps are in Schema Ownership.

Where this fits

Repo Relationship
forge-api Consumes the assembled schema as an embedded SQL file. No EF migrations. Its CI re-assembles this tree and fails on drift
forge-deploy Runs this repo's image as a one-shot reconcile before swapping the API, behind a backup and a destructive-change halt
forge-test Exercises the running stack, so it runs against whatever schema the reconcile produced

Product-level answers live on the hub, not here: Upgrades and Rollback for the operator's view of the reconcile, Backup and Restore for snapshots, Data Ownership and Export for what an export contains and what it means, and Architecture for how the components fit together.