glam/.opencode/rules/linkml/broaden-generic-predicate-ranges-rule.md

5.4 KiB

Rule 54: Broaden Generic Predicate Ranges Instead of Creating Bespoke Predicates

🚨 CRITICAL: When fixing gen-owl "Ambiguous type" warnings, broaden the range of generic predicates rather than creating specialized bespoke predicates.

The Problem

gen-owl "Ambiguous type" warnings occur when a slot is used as both:

  • DatatypeProperty (base range: string, integer, uri, etc.)
  • ObjectProperty (slot_usage override range: a class like Description, SubtitleFormatEnum)

This creates OWL ambiguity because OWL requires properties to be either DatatypeProperty OR ObjectProperty, not both.

WRONG Approach: Create Bespoke Predicates

# DON'T DO THIS - creates proliferation of rare-use predicates
slots:
  has_or_had_subtitle_format:    # Only used by VideoSubtitle
    range: SubtitleFormatEnum
  has_or_had_transcript_format:  # Only used by VideoTranscript
    range: TranscriptFormat

Why This Is Wrong:

  • Creates predicate proliferation (schema bloat)
  • Bespoke predicates are rarely reused across classes
  • Increases cognitive load for schema users
  • Fragments the ontology unnecessarily
  • Violates the principle of schema parsimony

CORRECT Approach: Broaden Generic Predicate Ranges

# DO THIS - make the generic predicate flexible enough
slots:
  has_or_had_format:
    range: uriorcurie  # Broadened from string
    description: |
      The format of a resource. Classes narrow this to specific
      enum types (SubtitleFormatEnum, TranscriptFormatEnum) via slot_usage.      

Then in class files, use slot_usage to narrow the range:

classes:
  VideoSubtitle:
    slots:
      - has_or_had_format
    slot_usage:
      has_or_had_format:
        range: SubtitleFormatEnum  # Narrowed for this class
        required: true

Range Broadening Options

Original Range Broadened Range When to Use
string uriorcurie When class overrides use URI-identified types or enums
string Any When truly polymorphic (strings AND class instances)
Specific class Common base class When multiple subclasses are used

Decision Tree

gen-owl warning: "Ambiguous type for: SLOTNAME"
    ↓
Is base slot range a primitive (string, integer, uri)?
├─ YES → Broaden to uriorcurie or Any
│        - Edit modules/slots/SLOTNAME.yaml
│        - Change range: string → range: uriorcurie
│        - Document change with Rule 54 reference
│        - Keep class-level slot_usage overrides (they narrow the range)
│
└─ NO → Consider if base slot needs common ancestor class
         - Create abstract base class if needed
         - Or broaden to uriorcurie

Implementation Workflow

  1. Identify warning: gen-owl ... 2>&1 | grep "Ambiguous type for:"

  2. Check base slot range:

    cat modules/slots/SLOTNAME.yaml | grep -A5 "^slots:" | grep "range:"
    
  3. Find class overrides:

    for f in modules/classes/*.yaml; do
      grep -l "SLOTNAME" "$f" && grep -A3 "SLOTNAME:" "$f" | grep "range:"
    done
    
  4. Broaden base range:

    • Edit modules/slots/SLOTNAME.yaml
    • Change range: stringrange: uriorcurie
    • Add annotation documenting the change
  5. Verify fix: Run gen-owl and confirm warning is gone

  6. Keep slot_usage overrides: Class-level range narrowing is fine and expected

Examples

Example 1: has_or_had_format

Before (caused warning):

# Base slot
slots:
  has_or_had_format:
    range: string  # DatatypeProperty

# Class override
classes:
  VideoSubtitle:
    slot_usage:
      has_or_had_format:
        range: SubtitleFormatEnum  # ObjectProperty → CONFLICT!

After (fixed):

# Base slot - broadened
slots:
  has_or_had_format:
    range: uriorcurie  # Now ObjectProperty-compatible

# Class override - unchanged, still narrows
classes:
  VideoSubtitle:
    slot_usage:
      has_or_had_format:
        range: SubtitleFormatEnum  # Valid narrowing

Example 2: has_or_had_hypernym

Before: range: string (DatatypeProperty) After: range: uriorcurie (ObjectProperty-compatible)

Classes that override to class ranges now work without ambiguity.

Validation

After broadening, run:

gen-owl 01_custodian_name_modular.yaml 2>&1 | grep "Ambiguous type for: SLOTNAME"

The warning should disappear without creating new predicates.

Anti-Patterns to Avoid

Anti-Pattern Correct Pattern
Create has_or_had_subtitle_format Broaden has_or_had_format to uriorcurie
Create has_or_had_entity_type Broaden has_or_had_type to uriorcurie
Create has_or_had_X_label Broaden has_or_had_label to uriorcurie
Create has_or_had_X_status Broaden has_or_had_status to uriorcurie

Rationale

This approach:

  1. Reduces schema complexity - Fewer predicates to understand
  2. Promotes reuse - Generic predicates work across domains
  3. Maintains OWL consistency - Single property type per predicate
  4. Preserves type safety - slot_usage still enforces class-specific ranges
  5. Follows semantic web best practices - Broad predicates, narrow contexts

See Also

  • Rule 38: Slot Centralization and Semantic URI Requirements
  • Rule: Slot Naming Convention (Current Style)
  • Rule 49: Slot Usage Minimization
  • LinkML Documentation: slot_usage