-- Ported from the SQLite migration of the same number. Same version, same -- meaning, different spelling: SQLite's storage classes and PostgreSQL's types -- do not line up, and one shared file would be a promise neither could keep. -- -- Times are milliseconds since the epoch in BIGINT, exactly as in SQLite, -- rather than TIMESTAMPTZ. The Go code reads and writes int64 milliseconds -- everywhere; converting here would mean two time representations in one -- codebase, and the conversion would be the thing that is wrong at 2am. -- Models added while the cluster is running. -- -- The catalog came from a YAML file read once at startup, so adding a model -- meant editing a file on the control plane and restarting it: a settling -- window, adoption churn, and on one occasion two minutes of outage. That is a -- reasonable way to manage a fixed production set and a bad one for a platform -- whose promise is that people can try things. -- -- These live alongside the ones in configuration rather than replacing them. -- Config-as-code stays declarative — a model in YAML is in the catalog on every -- start, whatever the database says — and this table holds the ones added -- afterwards. A model added here may not take a name or alias that -- configuration already uses, which the catalog's own constructor enforces for -- free, because it refuses a duplicate whatever the source. -- -- The definition is stored as JSON rather than as columns. A profile is a -- nested, evolving shape, and every field added to it would otherwise be a -- migration; the catalog already knows how to validate one, and a row that -- fails validation is reported and skipped rather than blocking startup. CREATE TABLE models ( name TEXT PRIMARY KEY, definition TEXT NOT NULL, created_at TIMESTAMP NOT NULL, created_by TEXT NOT NULL DEFAULT '' );