Add denial_reason, and sweep the three retired status names

FeatureInitializerService
Shipped
September 6, 2026 at 7:08 AM UTC
Author
Kamo
Commit
879e1a9

status_name holds a NAME read back through @Enumerated(STRING), so a row still saying TRIAGED after the constant is gone does not read as a default — it throws `No enum constant` and takes down every query that touches the row, the paged listing included. That is the failure the name-over-ordinal decision traded FOR: an ordinal shrink is silent, a name shrink is loud. The sweep runs AFTER the ordinal backfill, which is frozen history and still maps the column as it stood — rewriting that backfill to skip the three would silently reclassify rows on any database that has not run it yet. TRIAGED and CANNOT_REPRODUCE both meant open work, so both land on NEW where somebody will see them again; AWAITING_INFO does too, since NEW is the state that reopens the reporter's edit window, which is what that status existed to do. The dispatch audit's copy goes to NULL instead — that column records what an operator CHOSE, and null already means "they left the status alone", which is nearer the truth than claiming they picked one that no longer exists. denial_reason is nullable TEXT with no default. Unlike published, no default is needed for the ALTER to succeed on a populated table: Postgres only refuses ADD COLUMN without one when the column is NOT NULL. Applied by hand to the live database, which held zero rows in all three statuses. The statements ship anyway — "there were none when I looked" is not a property a migration can rely on.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing