# TimeClockEventVoid

## Description

TimeClockEventVoid is the **punch void journal** — an append-only record of every retraction (`VOID`) and un-retraction (`UNVOID`) applied to a `TimeClockEvent`. It exists because a raw punch is an immutable fact (ADR-014): a mistaken or duplicate punch is not edited or deleted, and "it was a mistake" is itself a new fact recorded here rather than a flag mutated on the event row (ADR-023).

Each entry captures `targetEventId` (the event being negated), `action` (`VOID` / `UNVOID`), a monotonic `sequence` within that target, the actor and instant (`actorId`, `occurredAt`), a `reasonCode`, a mandatory non-empty `reasonNote`, and the `authority` under which it was made. The **effective state** of a punch is decided by the highest-`sequence` record for its `targetEventId`: `VOID` means currently voided, `UNVOID` (or no record) means active. "Undo the void" appends an `UNVOID`; there is no deletion. Together with effective-dated generations and TimeCorrectionLog, this journal answers "who voided which punch, when, and why" for audit and labor-dispute defense.

## Domain Model Definitions

### Model type

AppendOnly

### Command Definitions

- voidPunch — append a VOID record retracting the target event (rejects if already voided)
- unvoidPunch — append an UNVOID record restoring a currently-voided target event

### Query Definitions

- (read via getPunch on TimeClockEvent, which resolves effective void state from this journal)

### Models

- TimeClockEventVoid

### Invariants

- Append-only: a record is never modified or deleted; the void/unvoid trail for an event is permanent (ADR-013, ADR-023)
- `action` is one of VOID, UNVOID, stored with normalized enum naming
- `sequence` is monotonic within a `targetEventId`; the highest-`sequence` record decides the target's effective void state
- A VOID is only valid when the target's latest record is not already a VOID; an UNVOID is only valid when the target's latest record is a VOID
- Every record references exactly one `TimeClockEvent` (via `targetEventId`); `actorId` is the authenticated caller's id, stored as a soft reference (not FK'd) so system/machine callers without a user-management `User` row can still be recorded
- Every record records a `reasonCode`, a non-empty `reasonNote`, an `occurredAt`, and an `authority` (SELF / MANAGER / ADMIN) — a void or unvoid without a reason is disallowed
- `reasonCode` is one of DUPLICATE, WRONG_PERSON, DEVICE_ERROR, MISTAKE, OTHER, stored with normalized enum naming

### Relationships

- **Negates a TimeClockEvent**: `targetEventId` references the immutable punch event this VOID/UNVOID applies to; the event row is never mutated
- **Recorded by caller**: `actorId` is the id of the authenticated caller who performed the void/unvoid — a soft reference, not FK'd to user-management `User`, since machine/system callers have no row there
- **Audit source**: with effective-dated generations and TimeCorrectionLog, forms the "who/when/what/why" trail that replaces the deprecated audit module (ADR-013)
