Appearance
ADR-009: Relationship Directionality
Status: Accepted Date: 2026-02-07
Context
Character relationships can be mutual (friends, siblings) or directional (mentor to student, sire to childe). The system needs to handle both cases while keeping the data model simple and avoiding data duplication or sync issues.
Decision
User chooses per relationship whether it is bidirectional or directional. A single row is stored regardless. An IsBidirectional flag controls query behavior.
How It Works
- Every relationship is stored as a single row with
SourceCharacterIdandTargetCharacterId IsBidirectional = true: queries return this relationship when searching from either character's perspectiveIsBidirectional = false: the relationship is directional from source to target only
Examples
| Relationship | Source | Target | Bidirectional | Query Behavior |
|---|---|---|---|---|
| Friends | Marcus | Elena | true | Shows on both Marcus and Elena's profiles |
| Mentor | Vivienne | Marcus | false | Shows Vivienne as Marcus's mentor; Marcus as Vivienne's student |
| Sire | The Baron | Marcus | false | Shows Baron as Marcus's sire; Marcus as Baron's childe |
| Rivals | Elena | Vivienne | true | Shows on both profiles |
Rationale
- Single row storage avoids the sync problems of creating two rows per relationship (if one is deleted, the other becomes orphaned)
- User control accommodates the domain reality that some relationships are inherently directional
- Query simplicity - for bidirectional relationships, the query checks both
SourceCharacterIdandTargetCharacterIdcolumns
Alternatives Considered
- Auto-create bidirectional rows - One API call creates two rows (A to B and B to A). Simpler queries but double storage and sync issues if one row is edited without the other
- Always single direction, display both - Store one row, always show from both sides. Doesn't handle genuinely directional relationships like mentor/student
- Separate relationship model per direction - Over-engineered for the expected complexity
Consequences
- Relationship queries must check both source and target columns when
IsBidirectional = true - The graph visualization endpoint assembles edges correctly based on directionality
- UI needs to present the bidirectional toggle during relationship creation
- Directional relationships may need inverse labels (e.g., "mentor" from source side, "student" from target side) - the
Subtypefield or a futureInverseSubtypefield can handle this