Enum and symbol-set evolution
Why is adding an enum value structurally safe and semantically breaking?
An enum (enumeration) is a type with a fixed set of allowed values, such as status ∈ {PICKED_UP, IN_TRANSIT, DELIVERED}.
This is the exception that shows the limits of every earlier mechanism. Adding a value to an enum is compatible by every test a registry applies: same type, same width, every old value still valid. And it is semantically breaking for any consumer that matches exhaustively: a CASE expression with no default branch, a check constraint, an inner join to a lookup table of statuses, a dashboard with hardcoded categories. That is Ravi's change 2.
The reason it escapes every mechanism: the compatibility check operates on the type, but the interface that matters is the domain (the set of valid values). The two are different, and only the type is checked.
Two resolutions exist:
- Model the symbol set as data: a reference table (a dimension) with its own lifecycle and its own consumers, instead of a type baked into the schema.
- Require every consumer to carry an explicit "unknown / other" branch. This is forward compatibility pushed down one level, from the field to the value. It has the same shape as the rule "readers must ignore unknown fields", which tells you it is the right generalization.
-- Brittle: any new status silently disappears into "Other"
CASE status WHEN 'PICKED_UP' THEN 'Active' WHEN 'DELIVERED' THEN 'Done' ELSE 'Other' END
-- Honest: the unknown branch is a visible, counted category
CASE status WHEN 'PICKED_UP' THEN 'Active' WHEN 'DELIVERED' THEN 'Done'
ELSE 'UNKNOWN:' || status ENDEnums are the case where the type is stable and the domain is the interface. Widen the domain and check the consumers, because no compatibility mode will.