Skip to content

D30 · Error keys are a Postgres enum

Accepted · 2026-10-08 · zuki · Closes OQ24, option E1

Decision: The list of error keys (D10) is a Postgres enum, public.error_key. A database function or trigger refuses through a helper that takes a key of that enum and its JSON detail, so a key the enum lacks can't be raised: SQLSTATE P0001 for a broken rule and 42501 for a missing permission (app.refuse and app.deny, as the Backend Engineer proposed). pnpm db:types turns the enum into a TypeScript union and a runtime list, and the one mapper in apps/web (D29) covers it exhaustively, so a key without Indonesian and English text fails typecheck. The table app.error_keys goes. What a key means is written in the Refusal line of the rule that raises it, and the generated Error keys page lists them all.

Why: The database owns the keys (D10), so it should own their list. An enum makes both ends checked with no extra tooling: Postgres refuses a key it doesn't know, and typecheck refuses a key apps/web has no text for. The other option, a registry in apps/web that CI compares with the keys raised in supabase/migrations, has to parse SQL and misses a key built at run time. The Backend and Frontend Engineers both chose this option in #47.

Consequences: A value added with ALTER TYPE … ADD VALUE can't be used as data in the same transaction; function bodies can use it at once, so only a migration that writes rows with a new key waits for the next migration. Enum values can't be dropped, which matches keys never being reused. The Backend Engineer changes BE pack B before zuki runs it and ships a new be-pack-b.zip (#11). FE pack D runs block 2a of FE-M0-13 and drops block 2b and scripts/error-keys.mjs, which answers FE-R7 (#18).