# Rotation-owned shifts reuse the `Shifts` table

A shift rotation owns its shifts inline at the domain level (rotations no longer reference Shift Templates), but those owned shifts are stored as ordinary rows in the existing `Shifts` table with `isTemplate=false`. `ShiftRotationDayShifts.shiftCode` keeps its current shape and resolves into `Shifts` exactly like it would for a template-backed reference. We chose this so that materializing a rotation onto the calendar (`ShiftAssignedShifts.shiftCode + shiftVersion`) reuses the existing soft-FK without inventing synthetic `Shift` rows at stamp time or making `shiftCode` nullable on assigned shifts.

## Considered Options

- **Inline storage on rotation tables** — extend `ShiftRotationDayShifts` with `name`/`color`/`description` and add a new `ShiftRotationDayShiftTimeSlots` child table. Rejected: stamping a rotation onto `ShiftAssignedShifts` would need to either (a) create synthetic `Shift` rows per stamped cell so the existing `shiftCode + shiftVersion` resolver keeps working, or (b) make `ShiftAssignedShifts.shiftCode` nullable when `shiftRotationId` is set and rewrite every grid/calendar read site to source the shift definition from rotation tables. Both options leak rotation-awareness into a large amount of shared read code.

## Consequences

- `Shifts` now holds three flavors: templates (`isTemplate=true`), ad-hoc one-off cell shifts (`isTemplate=false`, created at assignment time), and rotation-owned shifts (`isTemplate=false`, owned by a `ShiftRotation`). There is **no discriminator column** — rotation-ownership is recoverable through `ShiftRotationDayShifts.shiftCode`. The repository UI already filters `isTemplate=true`, so rotation shifts never surface there.
- Lifecycle of a rotation-owned `Shift` is bound to its rotation. Soft-deleting the rotation cascades to its owned `Shifts` via `deletedAt`. Removing a shift from a rotation edit also soft-deletes the underlying `Shift` row.
- Edits to a rotation-owned shift go through the existing `Shift` version-bump path (`updateShiftTemplate`-style): new row at `version+1` with `lastVersion=true`, old row flipped to `lastVersion=false`. Past `ShiftAssignedShifts.shiftCode + shiftVersion` references keep resolving to their stamped version (freeze-old-show-new). When retroactive-edit propagation lands, it will rewrite `ShiftAssignedTimeSlot` snapshots and bump `shiftVersion` pointers on existing assigned cells.
- Identity across edits is preserved by echoing `shiftCode` back in the PUT body. Missing `shiftCode` = create; present and matching this rotation's current codes = update via version-bump; present in DB but missing from body = soft-delete. Codes belonging to another rotation or a template are rejected.
- No new schema migrations are required for this change. Rotation-owned `Shifts` are created through the existing `shiftService.createShift`, which currently generates `uuidv4()` codes. The rotation graph's own ids (`ShiftRotation.id`, `ShiftRotationDay.id`, `ShiftRotationDayShift.id`) already use `uuidv7()`. A follow-up PR may converge `shiftService.createShift` to `uuidv7()` globally for index locality; the version mix is harmless until then because the column is a free-form `STRING` and no validator pins a version.
