glam/.opencode/rules/full-slot-migration-rule.md

12 KiB

Rule 53: Full Slot Migration - No Deprecation Notes

🚨 CRITICAL: When migrating slots from slot_fixes.yaml:

  1. Follow the revision section EXACTLY - The slot_fixes.yaml file specifies the exact replacement slots and classes to use
  2. Perform FULL MIGRATION - Completely remove the deprecated slot from the entity class
  3. Do NOT add deprecation notes - Never keep both old and new slots with deprecation markers

🚨 slot_fixes.yaml is AUTHORITATIVE AND CURATED 🚨

File Location: schemas/20251121/linkml/modules/slots/slot_fixes.yaml

THIS FILE IS THE SINGLE SOURCE OF TRUTH FOR ALL SLOT MIGRATIONS.

The slot_fixes.yaml file has been manually curated to specify the exact replacement slots and classes for each deprecated slot. The revisions are based on:

  1. Ontology analysis - Each replacement was chosen based on alignment with base ontologies (CIDOC-CRM, RiC-O, PROV-O, Schema.org, etc.)
  2. Semantic correctness - Revisions reflect the intended meaning of the original slot
  3. Pattern consistency - Follows established naming conventions (Rule 39: RiC-O style, Rule 43: singular nouns)
  4. Class hierarchy design - Type/Types pattern (Rule 0b) applied where appropriate

YOU MUST NOT:

  • Substitute different slots than those specified in revision
  • Use your own judgment to pick "similar" slots
  • Skip the revision and invent new mappings
  • Partially apply the revision (e.g., use the slot but not the class)

YOU MUST:

  • Follow the revision section TO THE LETTER
  • Use EXACTLY the slots and classes specified
  • Apply ALL components of the revision (both slots AND classes)
  • Interpret link_branch fields correctly (see below)
  • Update processed.status: true after completing migration

🚨 CRITICAL: The link_branch field in revision plans indicates nested class attributes. Items with link_branch: N are slots/classes that belong TO the primary class, not standalone replacements.

Revision Item Meaning
Items WITHOUT link_branch PRIMARY slot and class to create
Items WITH link_branch: 1 First attribute branch that the primary class needs
Items WITH link_branch: 2 Second attribute branch that the primary class needs
Items WITH link_branch: N Nth attribute branch for the primary class

Example: visitor_count Revision

- original_slot_id: https://nde.nl/ontology/hc/slot/visitor_count
  revision:
    - label: has_or_had_quantity     # PRIMARY SLOT (no link_branch)
      type: slot
    - label: Quantity                 # PRIMARY CLASS (no link_branch)
      type: class
    - label: has_or_had_measurement_unit   # Quantity needs this slot
      type: slot
      link_branch: 1                        # ← Branch 1: unit attribute
    - label: MeasureUnit                    # Range of has_or_had_measurement_unit
      type: class
      value:
        - visitors
      link_branch: 1
    - label: temporal_extent               # Quantity needs this slot too
      type: slot
      link_branch: 2                        # ← Branch 2: time attribute
    - label: TimeSpan                       # Range of temporal_extent
      type: class
      link_branch: 2

Interpretation: This creates:

  1. Primary: has_or_had_quantity slot → Quantity class
  2. Branch 1: Quantity.has_or_had_measurement_unitMeasureUnit (with value "visitors")
  3. Branch 2: Quantity.temporal_extentTimeSpan

Resulting Class Structure

# The Quantity class should have these slots:
Quantity:
  slots:
    - has_or_had_measurement_unit  # From link_branch: 1
    - temporal_extent              # From link_branch: 2

Complex Example: visitor_conversion_rate

- original_slot_id: https://nde.nl/ontology/hc/slot/visitor_conversion_rate
  revision:
    - label: has_or_had_conversion_rate    # PRIMARY SLOT
      type: slot
    - label: ConversionRate                 # PRIMARY CLASS
      type: class
    - label: has_or_had_type               # ConversionRate.has_or_had_type
      type: slot
      link_branch: 1
    - label: ConversionRateType            # Abstract type class
      type: class
      link_branch: 1
    - label: includes_or_included          # ConversionRateType hierarchy slot
      type: slot  
      link_branch: 1
    - label: ConversionRateTypes           # Concrete subclasses file
      type: class
      link_branch: 1
    - label: temporal_extent               # ConversionRate.temporal_extent
      type: slot
      link_branch: 2
    - label: TimeSpan                      # Range of temporal_extent
      type: class
      link_branch: 2

Interpretation:

  1. Primary: has_or_had_conversion_rateConversionRate
  2. Branch 1: Type hierarchy with ConversionRateType (abstract) + ConversionRateTypes (concrete subclasses)
  3. Branch 2: Temporal tracking via temporal_extentTimeSpan
  • Create/verify PRIMARY slot exists
  • Create/verify PRIMARY class exists
  • For EACH link_branch: N:
    • Add the branch slot to PRIMARY class's slots: list
    • Import the branch slot file
    • Import the branch class file (if creating new class)
    • Verify range of branch slot points to branch class
  • Update consuming class to use PRIMARY slot (not deprecated slot)
  • Update examples to show nested structure

Mandatory: Follow slot_fixes.yaml Revisions Exactly

The revision section in slot_fixes.yaml is AUTHORITATIVE. Do not substitute different slots based on your own judgment.

Example from slot_fixes.yaml:

- original_slot_id: https://nde.nl/ontology/hc/slot/actual_start
  revision:
    - label: begin_of_the_begin   # ← USE THIS SLOT
      type: slot
    - label: TimeSpan             # ← USE THIS CLASS
      type: class

CORRECT: Use begin_of_the_begin slot (as specified) WRONG: Substitute has_actual_start_date (not in revision)

The Problem

Adding deprecation notes while keeping both old and new slots:

  • Creates schema bloat with redundant properties
  • Confuses data consumers about which slot to use
  • Violates single-source-of-truth principle
  • Complicates future data validation

Anti-Pattern (WRONG)

# WRONG - Keeping deprecated slot with deprecation note
classes:
  TemporaryLocation:
    slots:
      - actual_start        # OLD - kept with deprecation note
      - actual_end          # OLD - kept with deprecation note
      - has_actual_start_date  # NEW
      - has_actual_end_date    # NEW
    slot_usage:
      actual_start:
        deprecated: |
          DEPRECATED: Use has_actual_start_date instead.          
        # ... more deprecation documentation

Correct Pattern

# CORRECT - Only new slots, old slots completely removed
classes:
  TemporaryLocation:
    slots:
      - has_actual_start_date  # NEW - only new slots present
      - has_actual_end_date    # NEW
    # NO slot_usage for deprecated slots - they don't exist in this class

Migration Steps

When processing a slot from slot_fixes.yaml:

  1. Identify affected entity class(es)
  2. Remove old slot from imports (if dedicated import file exists)
  3. Remove old slot from slots list
  4. Remove any slot_usage for old slot
  5. Add new slot import (if not already present)
  6. Add new slot to slots list
  7. Add slot_usage for new slot (if range override or customization needed)
  8. Update examples to use new slot
  9. Validate with gen-owl

What Happens to Old Slot Files

The old slot files in modules/slots/ (e.g., actual_start.yaml, activities_societies.yaml) are NOT deleted because:

  • Other entity classes might still use them
  • They serve as documentation of the old schema
  • They can be archived when all usages are migrated

However, the old slots are removed from the entity class being migrated.

Example: TemporaryLocation Migration

Before (with old slots):

imports:
  - ../slots/actual_end
  - ../slots/actual_start
  - ../slots/has_actual_start_date
  - ../slots/has_actual_end_date

slots:
  - actual_end
  - actual_start
  - has_actual_start_date
  - has_actual_end_date

After (fully migrated):

imports:
  # actual_end and actual_start imports REMOVED
  - ../slots/has_actual_start_date
  - ../slots/has_actual_end_date

slots:
  # actual_end and actual_start REMOVED from list
  - has_actual_start_date
  - has_actual_end_date

Slot Usage for New Slots

Only add slot_usage for the new slot if you need to:

  • Override the range for this specific class
  • Add class-specific examples
  • Add class-specific constraints

Do NOT add slot_usage just to document that it replaces an old slot.

Recording in slot_fixes.yaml

When marking a slot as processed:

- original_slot_id: https://nde.nl/ontology/hc/slot/actual_start
  processed:
    status: true
    timestamp: '2026-01-14T16:00:00Z'
    session: "session-2026-01-14-type-migration"
    notes: "FULLY MIGRATED: TemporaryLocation - actual_start REMOVED, using temporal_extent with TimeSpan.begin_of_the_begin (Rule 53)"

Note the "FULLY MIGRATED" prefix in notes to confirm this was a complete removal, not a deprecation-in-place.


⚠️ Common Mistakes to Avoid ⚠️

Mistake 1: Substituting Different Slots

slot_fixes.yaml specifies:

- original_slot_id: https://nde.nl/ontology/hc/slot/actual_start
  revision:
    - label: begin_of_the_begin   # ← MUST USE THIS
      type: slot
    - label: TimeSpan             # ← WITH THIS CLASS
      type: class
Action Status
Using begin_of_the_begin with TimeSpan CORRECT
Using has_actual_start_date (invented) WRONG
Using start_date (different slot) WRONG
Using begin_of_the_begin WITHOUT TimeSpan WRONG (incomplete)

Mistake 2: Partial Application

The revision often specifies MULTIPLE components that work together:

revision:
  - label: has_or_had_type       # ← Slot for linking
    type: slot
  - label: BackupType            # ← Abstract base class
    type: class
  - label: includes_or_included  # ← Slot for hierarchy
    type: slot
  - label: BackupTypes           # ← Concrete subclasses
    type: class

All four components are part of the migration. Don't just use has_or_had_type and ignore the class structure.

Mistake 3: Using temporal_extent Slot Correctly

When slot_fixes.yaml specifies TimeSpan-based revision:

revision:
  - label: begin_of_the_begin
    type: slot
  - label: TimeSpan
    type: class

This means: Use the temporal_extent slot (which has range: TimeSpan) and access the temporal bounds via TimeSpan's slots:

# CORRECT: Use temporal_extent with TimeSpan structure
temporal_extent:
  begin_of_the_begin: '2020-06-15'
  end_of_the_end: '2022-03-15'

# WRONG: Create new has_actual_start_date slot
has_actual_start_date: '2020-06-15'  # ❌ Not in revision!

Mistake 4: Not Updating Examples

When migrating slots, update ALL examples in the class file:

  • Description examples (in class description)
  • slot_usage examples
  • Class-level examples (at bottom of file)

Verification Checklist

Before marking a slot as processed:

  • Read the revision section completely
  • Identified ALL slots and classes in revision
  • Removed old slot from imports
  • Removed old slot from slots list
  • Removed old slot from slot_usage
  • Added new slot(s) per revision
  • Added new class import(s) per revision
  • Updated ALL examples to use new slots
  • Validated with linkml-lint or gen-owl
  • Updated slot_fixes.yaml with:
    • status: true
    • timestamp (ISO 8601)
    • session identifier
    • notes with "FULLY MIGRATED:" prefix

See Also

  • Rule 9: Enum-to-Class Promotion (single source of truth principle)
  • Rule 0b: Type/Types File Naming Convention
  • Rule: Slot Naming Convention (Current Style)
  • .opencode/ENUM_TO_CLASS_PRINCIPLE.md
  • schemas/20251121/linkml/modules/slots/slot_fixes.yaml - AUTHORITATIVE master list of migrations