# RecordPunch

## Permission Scope

`punch`

## Overview

RecordPunch appends a single immutable raw punch event (clock-in, clock-out, break start/end, or step-out/step-in) for a worker's Assignment, captured from whatever source device or channel the worker used.

## Business Rules

- An event is append-only: recording a punch never modifies or deletes an existing `TimeClockEvent` (ADR-014, ADR-023)
- `eventType` must be one of CLOCK_IN, CLOCK_OUT, BREAK_START, BREAK_END, STEP_OUT, STEP_IN, stored with normalized enum naming
- `source` must be one of WEB, MOBILE, KIOSK, IC_CARD, BIOMETRIC, PC_LOGON, SLACK, IMPORT, stored with normalized enum naming
- The event must reference exactly one workforce Assignment, resolved as the Assignment effective on `occurredAt`
- `occurredAt` is required and is the real-world instant of the punch; no workday is computed or stored at punch time — the day-breaker assigns the workday later during block formation
- An overnight punch (e.g. a clock-out after midnight) is stored with its true `occurredAt`; it is not itself assigned to a workday at this step (issue #7)
- Raw events are inputs to derivation only; they are never read directly by payroll, costing, or Article-36 agreement

## Process Flow

```mermaid
flowchart TD
    A[Worker punches via WEB/MOBILE/KIOSK/IC_CARD/BIOMETRIC/PC_LOGON/SLACK] --> B[Resolve Assignment effective on occurredAt]
    B --> C{Assignment found?}
    C -- No --> D[Reject: ASSIGNMENT_NOT_FOUND]
    C -- Yes --> E[Validate eventType and source enums]
    E --> F{Valid?}
    F -- No --> G[Reject: INVALID_EVENT_TYPE / INVALID_SOURCE]
    F -- Yes --> H[Append immutable TimeClockEvent with eventType, occurredAt, source]
    H --> I[Event available for block formation by day-breaker]
```

## External Dependencies

- [workforce::Assignment](../model/TimeClockEvent.md) - resolves the Assignment effective on `occurredAt` to determine whose punch this is

## Error Scenarios

- **ASSIGNMENT_NOT_FOUND**: no workforce Assignment is effective for the relevant worker/date
- **INVALID_EVENT_TYPE**: `eventType` is not one of CLOCK_IN, CLOCK_OUT, BREAK_START, BREAK_END, STEP_OUT, STEP_IN
- **OCCURRED_AT_REQUIRED**: `occurredAt` is missing

## Test Cases

- recording a punch appends a TimeClockEvent linked to the Assignment effective on its occurredAt
- `eventType` is stored with normalized enum naming and `source` is stored as the provided free-form key
- an overnight clock-out is stored with its real occurredAt and is not itself assigned a workday at punch time
- recording a punch never modifies or deletes an existing event
- rejects with ASSIGNMENT_NOT_FOUND when no Assignment is in force on occurredAt
- rejects with INVALID_EVENT_TYPE when eventType is not one of the recognized enum values
- rejects with OCCURRED_AT_REQUIRED when occurredAt is missing
