# ChangeDetail

## Description

ChangeDetail records the old and new values for a single field that changed within a business operation. Each ChangeDetail is linked to a parent AuditEntry via auditEntryId and captures the fieldName, previous value (oldValue), and new value (newValue) as serialized strings. Values are subject to the sensitivity mode defined in the applicable audit policy: CAPTURE stores raw values, MASK stores masked representations, HASH stores one-way hashes, and EXCLUDE stores null for both values while still recording that the field changed. Values exceeding 4,000 characters are truncated with a `[truncated]` suffix after sensitivity mode processing.

Examples: "unitPrice changed from '100.00' to '95.00'", "salary changed (MASK mode: '***' to '***')".

## Domain Model Definitions

### Model type

AppendOnly

### Command Definitions

- [logAuditEvent](../commands/LogAuditEvent.md) - ChangeDetail records are created as part of the logAuditEvent command, linked to the parent AuditEntry

### Query Definitions

- [getChangeDetails](../queries/GetChangeDetails.md) - Retrieve all field-level change details for a given audit entry

### Models

- ChangeDetail

### Invariants

- Each ChangeDetail belongs to exactly one parent AuditEntry via auditEntryId
- fieldName is required and must be non-empty
- Values are stored as serialized strings regardless of original data type (integer, boolean, date, etc.)
- For CREATE operations, oldValue is null and newValue contains the initial value
- For DELETE operations, oldValue contains the last known value and newValue is null
- For UPDATE operations, both oldValue and newValue are populated, but only for fields that actually changed
- A save operation with no field changes produces zero ChangeDetail entries (no-op detection)
- A CREATE operation on a record with N populated fields produces N ChangeDetail entries
- Once persisted, ChangeDetail records cannot be modified or deleted (immutable)
- Binary / BLOB fields are excluded from capture entirely and are not eligible for policy field rules
- Computed / derived fields are excluded from capture
- Long text fields (> 4,000 characters) are truncated to 4,000 characters with a `[truncated]` suffix
- Rich text / HTML field values are stripped to plain text before storage, then truncated if needed
- Collection / array field values are serialized as JSON strings and truncated to 4,000 characters if the serialized form exceeds the limit
- Relation field values capture the foreign key ID as a string
- File attachment fields capture reference metadata (file ID, filename, size), not the file content
- Truncation is applied after sensitivity mode processing (MASK/HASH/CAPTURE)
- When sensitivity mode is CAPTURE (default), raw values are stored
- When sensitivity mode is MASK, oldValue and newValue store masked representations
- When sensitivity mode is HASH, oldValue and newValue store one-way hashes
- When sensitivity mode is EXCLUDE, both oldValue and newValue are null, but the ChangeDetail record is still created to indicate the field changed
- Fields without an explicit sensitivity mode in the policy default to CAPTURE

### Relationships

- **Belongs To AuditEntry**: Each ChangeDetail is linked to exactly one AuditEntry via auditEntryId
- **Governed By PolicyFieldRule**: Sensitivity mode applied to stored values is determined by the PolicyFieldRule associated with the parent AuditEntry's applicable AuditPolicy
