glam/.opencode/ENUM_TO_CLASS_PRINCIPLE.md
2025-12-06 19:50:04 +01:00

6.2 KiB

Enum-to-Class Principle: Single Source of Truth

Version: 1.0
Last Updated: 2025-12-06
Status: Active Rule


Core Principle

Enums are TEMPORARY scaffolding. Once an enum is promoted to a class hierarchy, the enum MUST be deleted to maintain a Single Source of Truth.


Rationale

The Problem: Dual Representation

When both an enum AND a class hierarchy exist for the same concept:

  • Data sync issues: Enum values and class names can drift apart
  • Maintenance burden: Changes must be made in two places
  • Developer confusion: Which one should I use?
  • Validation conflicts: Enum constraints vs class ranges may diverge

The Solution: Single Source of Truth

  • Enums: Use for simple, fixed value constraints (e.g., DataTierEnum: TIER_1, TIER_2, TIER_3, TIER_4)
  • Classes: Use when the concept needs properties, relationships, or rich documentation
  • NEVER BOTH: Once promoted to classes, DELETE the enum

When to Promote Enum to Classes

Promote when the concept needs:

Need Enum Can Do? Class Required?
Fixed value constraint Yes Yes
Properties (e.g., role_category, typical_domains) No Yes
Rich description per value Limited Yes
Relationships to other entities No Yes
Inheritance hierarchy No Yes
Independent identity (URI) Limited Yes
Ontology class mapping (class_uri) Via meaning Native

Rule of thumb: If you're adding detailed documentation to each enum value, or want to attach properties, it's time to promote to classes.


Promotion Workflow

Step 1: Create Class Hierarchy

# modules/classes/StaffRole.yaml (base class)
StaffRole:
  abstract: true
  description: Base class for staff role categories
  slots:
    - role_id
    - role_name
    - role_category
    - typical_domains

# modules/classes/StaffRoles.yaml (subclasses)
Curator:
  is_a: StaffRole
  description: Museum curator specializing in collection research...
  
Conservator:
  is_a: StaffRole
  description: Conservator specializing in preservation...

Step 2: Update Slot Ranges

# BEFORE (enum)
staff_role:
  range: StaffRoleTypeEnum

# AFTER (class)
staff_role:
  range: StaffRole

Step 3: Update Modular Schema Imports

# REMOVE enum import
# - modules/enums/StaffRoleTypeEnum  # DELETED

# ADD class imports
- modules/classes/StaffRole
- modules/classes/StaffRoles

Step 4: Archive the Enum

mkdir -p schemas/.../archive/enums
mv modules/enums/OldEnum.yaml archive/enums/OldEnum.yaml.archived_$(date +%Y%m%d)

Step 5: Document the Change

  • Update archive/enums/README.md with migration entry
  • Add comment in modular schema explaining removal
  • Update any documentation referencing the old enum

Example: StaffRoleTypeEnum → StaffRole

Before (2025-12-05):

# StaffRoleTypeEnum.yaml
StaffRoleTypeEnum:
  permissible_values:
    CURATOR:
      description: Museum curator
    CONSERVATOR:
      description: Conservator
    # ... 51 values with limited documentation

After (2025-12-06):

# StaffRole.yaml (abstract base)
StaffRole:
  abstract: true
  slots:
    - role_id
    - role_name
    - role_category
    - typical_domains
    - typical_responsibilities
    - requires_qualification

# StaffRoles.yaml (51 subclasses)
Curator:
  is_a: StaffRole
  class_uri: schema:curator
  description: |
    Museum curator specializing in collection research...
    
    **IMPORTANT - FORMAL TITLE vs DE FACTO WORK**:
    This is the OFFICIAL job appellation/title. Actual work may differ.    
  slot_usage:
    role_category:
      equals_string: CURATORIAL
    typical_domains:
      equals_expression: "[Museums, Galleries]"

Why the promotion?

  1. Need to distinguish FORMAL TITLE from DE FACTO WORK
  2. Each role has role_category, common_variants, typical_domains, typical_responsibilities
  3. Roles benefit from inheritance (Curator is_a StaffRole)
  4. Richer documentation per role

Enums That Should REMAIN Enums

Some enums are appropriate as permanent fixtures:

Enum Why Keep as Enum
DataTierEnum Simple 4-value tier (TIER_1 through TIER_4), no properties needed
DataSourceEnum Fixed source types, simple strings
CountryCodeEnum ISO 3166-1 standard, no custom properties
LanguageCodeEnum ISO 639 standard, no custom properties

Characteristics of "permanent" enums:

  • Based on external standards (ISO, etc.)
  • Simple values with no need for properties
  • Unlikely to require rich per-value documentation
  • Used purely for validation/constraint

Anti-Patterns

WRONG: Keep Both Enum and Classes

# modules/enums/StaffRoleTypeEnum.yaml  # ← Still exists!
# modules/classes/StaffRole.yaml        # ← Also exists!
# Which one is authoritative? CONFUSION!

WRONG: Create Classes but Keep Enum "for backwards compatibility"

# "Let's keep the enum for old code"
# Result: Two sources of truth, guaranteed drift

CORRECT: Delete Enum After Creating Classes

# modules/enums/StaffRoleTypeEnum.yaml  # ← ARCHIVED
# modules/classes/StaffRole.yaml        # ← Single source of truth
# modules/classes/StaffRoles.yaml       # ← All 51 role subclasses

Verification Checklist

After promoting an enum to classes:

  • Old enum file moved to archive/enums/
  • Modular schema import removed for enum
  • Modular schema import added for new class(es)
  • All slot ranges updated from enum to class
  • No grep results for old enum name in active schema files
  • archive/enums/README.md updated with migration entry
  • Comment added in modular schema explaining removal
# Verify enum is fully removed (should return only archive hits)
grep -r "StaffRoleTypeEnum" schemas/20251121/linkml/

See Also