# Enum vs Class: Single Source of Truth Principle **Document Type**: Design Decision **Status**: Active **Last Updated**: 2025-12-06 --- ## Executive Summary This project follows a strict **Single Source of Truth** principle for schema elements. When a concept transitions from an enum (simple value list) to a class hierarchy (rich structure with properties), the original enum **MUST be deleted** to prevent dual representation. --- ## Background ### What are Enums? LinkML enums define constrained value sets: ```yaml DataTierEnum: permissible_values: TIER_1_AUTHORITATIVE: description: Authoritative source TIER_2_VERIFIED: description: Verified data ``` **Enums are good for**: - Simple value constraints - Values based on external standards (ISO codes) - Values that don't need properties or relationships ### What are Classes? LinkML classes define structured entities with properties: ```yaml Curator: is_a: StaffRole class_uri: schema:curator slots: - role_category - typical_domains - common_variants ``` **Classes are good for**: - Concepts needing properties - Inheritance hierarchies - Rich documentation per value - Relationships to other entities --- ## The Rule > **When an enum is promoted to a class hierarchy, the enum MUST be deleted.** ### Why This Matters | Scenario | Single Source | Dual Source | |----------|--------------|-------------| | Where is the definition? | One place | Two places (confusion) | | Where do I make changes? | One file | Two files (maintenance burden) | | Can they drift apart? | No | Yes (bugs) | | Which is authoritative? | Clear | Ambiguous | --- ## Decision Workflow ``` Is this concept a simple value constraint? │ ├─ YES → Use enum │ Examples: DataTierEnum, CountryCodeEnum │ └─ NO, it needs properties/relationships/rich docs ↓ Create class hierarchy ↓ Update slot ranges: enum → class ↓ DELETE the enum (archive it) ↓ Document the migration ``` --- ## Case Study: StaffRoleTypeEnum → StaffRole ### Before (Enum) ```yaml # StaffRoleTypeEnum.yaml StaffRoleTypeEnum: permissible_values: CURATOR: description: Museum curator CONSERVATOR: description: Conservator ``` **Limitations**: - Can't distinguish formal title from de facto work - Can't add `role_category`, `typical_domains`, `common_variants` - Can't express inheritance (e.g., `DataScientist is_a DataRole`) ### After (Classes) ```yaml # StaffRole.yaml (abstract base) StaffRole: abstract: true description: | Base class for staff role categories. **IMPORTANT**: These are FORMAL job appellations/titles. Actual de facto work may differ or stretch beyond these classifications. slots: - role_id - role_name - role_category - typical_domains - typical_responsibilities # StaffRoles.yaml (51 subclasses) Curator: is_a: StaffRole class_uri: schema:curator slot_usage: role_category: equals_string: CURATORIAL ``` ### Migration Steps Taken 1. Created `modules/classes/StaffRole.yaml` (base class with RoleCategoryEnum) 2. Created `modules/classes/StaffRoles.yaml` (51 specialized subclasses) 3. Updated `modules/slots/staff_role.yaml`: `range: StaffRoleTypeEnum` → `range: StaffRole` 4. Updated `modules/classes/PersonObservation.yaml` slot definition 5. Updated `01_custodian_name_modular.yaml` imports (removed enum, added classes) 6. Archived enum to `archive/enums/StaffRoleTypeEnum.yaml.archived_20251206` --- ## Enums That Remain Permanent Not all enums should become classes. These remain as enums: | Enum | Rationale | |------|-----------| | `DataTierEnum` | Simple 4-tier hierarchy, no properties needed | | `DataSourceEnum` | Fixed source types, external classification | | `InstitutionTypeEnum` | Matches GLAMORCUBESFIXPHDNT taxonomy, single-letter codes | | `CountryCodeEnum` | ISO 3166-1 standard, external authority | | `LanguageCodeEnum` | ISO 639 standard, external authority | **Indicators of permanent enums**: - Based on external standards with defined values - Values are simple strings without properties - No need for inheritance or relationships - Used purely for validation constraints --- ## Verification After any enum-to-class migration: ```bash # Ensure enum is only in archive grep -r "StaffRoleTypeEnum" schemas/20251121/linkml/modules/ # Should return NO results # Verify classes are imported grep "StaffRole" schemas/20251121/linkml/01_custodian_name_modular.yaml # Should show class imports ``` --- ## Related Documentation - `.opencode/ENUM_TO_CLASS_PRINCIPLE.md` - Agent-facing rules - `schemas/20251121/linkml/archive/enums/README.md` - Archive directory - LinkML Enums: https://linkml.io/linkml/schemas/enums.html - LinkML Classes: https://linkml.io/linkml/schemas/models.html --- ## Change Log | Date | Change | |------|--------| | 2025-12-06 | Initial document created | | 2025-12-06 | StaffRoleTypeEnum migrated to StaffRole class hierarchy |