Files
initiative/specs/002-add-combatant/research.md

41 lines
2.5 KiB
Markdown

# Research: Add Combatant
**Feature**: 002-add-combatant
**Date**: 2026-03-03
## Research Summary
No NEEDS CLARIFICATION items existed in the technical context. The feature is straightforward and follows established patterns. Research focused on confirming existing patterns and the one key design decision.
## Decision 1: CombatantId Generation Strategy
**Decision**: CombatantId is passed into the domain function as an argument, not generated internally.
**Rationale**: The domain layer must remain pure and deterministic (Constitution Principle I). Generating IDs internally would require either randomness (UUID) or side effects (counter with mutable state), both of which violate purity. By accepting the id as input, `addCombatant(encounter, id, name)` is a pure function: same inputs always produce the same output.
**Alternatives considered**:
- Generate UUID inside domain: Violates deterministic core principle. Tests would be non-deterministic.
- Pass an id-generator function: Adds unnecessary complexity. The application layer can generate the id and pass it in.
**Who generates the id**: The application layer (use case) or adapter layer (hook) generates the CombatantId before calling the domain function. This matches how `createEncounter` already works — callers construct `Combatant` objects with pre-assigned ids.
## Decision 2: Function Signature Pattern
**Decision**: Follow the `advanceTurn` pattern — standalone pure function returning a success result or DomainError.
**Rationale**: Consistency with the existing codebase. `advanceTurn` returns `AdvanceTurnSuccess | DomainError`, so `addCombatant` will return `AddCombatantSuccess | DomainError` with the same shape: `{ encounter, events }`.
**Alternatives considered**:
- Method on an Encounter class: Project uses plain interfaces and free functions, not classes.
- Mutating the encounter in place: Violates immutability convention (all fields are `readonly`).
## Decision 3: Name Validation Approach
**Decision**: Trim whitespace, then reject empty strings. The domain function validates the name.
**Rationale**: Name validation is a domain rule (what constitutes a valid combatant name), so it belongs in the domain layer. Trimming before checking prevents whitespace-only names from slipping through.
**Alternatives considered**:
- Validate in application layer: Would allow invalid data to reach domain if called from a different adapter. Domain should protect its own invariants.
- Accept any string: Would allow empty-name combatants, violating spec FR-004.