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

179 lines
5.4 KiB
Markdown

# 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
```yaml
# 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
```yaml
# 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:
```yaml
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**:
```bash
cat modules/slots/SLOTNAME.yaml | grep -A5 "^slots:" | grep "range:"
```
3. **Find class overrides**:
```bash
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: string``range: 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)**:
```yaml
# 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)**:
```yaml
# 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:
```bash
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](https://linkml.io/linkml-model/latest/docs/slot_usage/)