Skip to content

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 SourceCharacterId and TargetCharacterId
  • IsBidirectional = true: queries return this relationship when searching from either character's perspective
  • IsBidirectional = false: the relationship is directional from source to target only

Examples

RelationshipSourceTargetBidirectionalQuery Behavior
FriendsMarcusElenatrueShows on both Marcus and Elena's profiles
MentorVivienneMarcusfalseShows Vivienne as Marcus's mentor; Marcus as Vivienne's student
SireThe BaronMarcusfalseShows Baron as Marcus's sire; Marcus as Baron's childe
RivalsElenaViviennetrueShows 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 SourceCharacterId and TargetCharacterId columns

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 Subtype field or a future InverseSubtype field can handle this