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

235 lines
6.2 KiB
Markdown

# 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
```yaml
# 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
```yaml
# BEFORE (enum)
staff_role:
range: StaffRoleTypeEnum
# AFTER (class)
staff_role:
range: StaffRole
```
### Step 3: Update Modular Schema Imports
```yaml
# REMOVE enum import
# - modules/enums/StaffRoleTypeEnum # DELETED
# ADD class imports
- modules/classes/StaffRole
- modules/classes/StaffRoles
```
### Step 4: Archive the Enum
```bash
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):
```yaml
# StaffRoleTypeEnum.yaml
StaffRoleTypeEnum:
permissible_values:
CURATOR:
description: Museum curator
CONSERVATOR:
description: Conservator
# ... 51 values with limited documentation
```
**After** (2025-12-06):
```yaml
# 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
```yaml
# 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"
```yaml
# "Let's keep the enum for old code"
# Result: Two sources of truth, guaranteed drift
```
### ✅ CORRECT: Delete Enum After Creating Classes
```yaml
# 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
```bash
# Verify enum is fully removed (should return only archive hits)
grep -r "StaffRoleTypeEnum" schemas/20251121/linkml/
```
---
## See Also
- `docs/ENUM_CLASS_SINGLE_SOURCE.md` - Extended documentation
- `schemas/20251121/linkml/archive/enums/README.md` - Archive directory
- LinkML documentation on enums: https://linkml.io/linkml/schemas/enums.html
- LinkML documentation on classes: https://linkml.io/linkml/schemas/models.html