# 0013. Status changes are append-only logs

- **Status:** Accepted
- **Date:** 2025-07-20

## Context

A rental or an invoice is a commercial document. "When did this become active, and who did it" is a question
the office actually asks — in a dispute, in a handover, when reconciling with Billingo.

## Decision

**The current status is denormalised onto the record; the history is a separate append-only table.**

| Owner | History table |
|-------|---------------|
| `Rent` | `RentStatusChange` |
| `Payment` | `PaymentStatusChange` |
| `ProposalRequest` | `ProposalRequestStatusChange` |

Each history row carries the owner, the new status, the user who made the change and the timestamp. Rows are
never updated or deleted.

The same shape is used for the typed event logs: `RentEvent` + `RentEventType`, `LocationEvent` +
`LocationEventType`.

## Alternatives

- **Status column only.** Cheapest, and loses the question the office asks.
- **History only, current status derived** from the latest row. Correct, and turns every list query into a
  correlated subquery or a join — the rentals grid, the dashboard counters and every occupancy check would
  pay for it.
- **A generic audit-log table for every model.** More than the problem needs, and worse to query for the one
  case that matters.

## Consequences

- **List queries stay simple.** `WHERE status = 'ACTIVE'` is an indexed column lookup.
- **The two can diverge.** Writing `status` directly without appending a `*StatusChange` row leaves the audit
  trail wrong, and **nothing enforces the pairing** — it is a convention held up by the admin actions being
  the only writers. Any console command or manual SQL that touches `status` has to write the history row
  itself.
- The history tables grow without bound. At this volume that is not a concern.
- `RentStatusChange` links to `User`, so the user relation is part of the audit value — a change made by a
  console command has no user.
