Appearance
Entity: readingActionLog
Entity Type: Database table
Description: A domain-specific action log recording every value-changing action taken on a reading row — who changed it, when, and the value before and after. Entries outlive the reading they describe: readingId, readAt and gaugeId are loose references (plain columns, no FOREIGN KEY), since an enforced FK to the reading hypertable would make compress_chunk() fail and would block the very delete this log has to record. importId, by contrast, is a real, enforced Foreign Key to dataImport.id — dataImport is an ordinary table, not a hypertable, so it carries none of that risk. The row is create-only — no update or delete method exists on its repository port — and it carries no soft-delete column: once written, it is permanent.
Data Attributes Table
| Attribute Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
| id | Primary key of the entity. Application-supplied — no DB-level default. | UUID | - | Yes | Yes | UUID v7 | - | Primary Key | 018fa51f-fda1-79f4-8461-2cb8f1cabc10 |
| tenantId | Tenant this record belongs to. | UUID | - | Yes | No | UUID v7 | Foreign Key → tenant; must exist | - | 018fa51f-fda3-7c63-b5a9-3fa33dc989de |
| readingId | The reading this action concerns. Loose reference — no FOREIGN KEY, deliberately, so the log survives the reading's own deletion or hypertable compression. | UUID | - | Yes | No | UUID v7 | - | name: idx_reading_action_log_reading (reading_id, created_at DESC, id DESC), type: btree | 018fa51f-fda9-... |
| readAt | The reading's own timestamp, carried alongside readingId so the log stays meaningful after the reading row is gone. Loose reference, same reasoning as readingId. | Timestamp with time zone | - | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | - | - | 2026-03-01T00:00:00Z |
| gaugeId | The gauge the reading belongs to. Loose reference, same reasoning as readingId. | UUID | - | Yes | No | UUID v7 | - | - | 018fa51f-... |
| action | What happened to the reading. create is defined by the CHECK constraint and the enum but has no producer in code today. | String (enum) | - | Yes | No | enum: create, edit, delete, clearFinal | Must be one of the listed values | - | edit |
| valueBefore | The reading's value before this action. | Double precision, nullable | null | conditional | No | - | reading_action_log_values_per_action CHECK — must be NULL for create/clearFinal, NOT NULL for edit/delete | - | 1024.5 |
| valueAfter | The reading's value after this action. | Double precision, nullable | null | conditional | No | - | Same CHECK as valueBefore, mirrored — must be NULL for delete/clearFinal, NOT NULL for create/edit | - | 1080.2 |
| note | Free-text note about the action. | String, nullable | null | No | No | - | - | - | Corrected per customer complaint |
| importId | The import batch that produced the original reading, if any. Real, enforced Foreign Key — see Description. | UUID, nullable | null | No | No | UUID v7 | Foreign Key → dataImport.id | name: idx_reading_action_log_import, type: btree | 018fa51f-... |
| createdAt | Timestamp this log row was written. Immutable — the row is never updated. | Timestamp with time zone | now() | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | - | - | 2026-03-01T09:00:00Z |
| updatedAt | Present for schema consistency (auditFields mixin); never meaningfully changed, since the row is create-only. | Timestamp with time zone | now() | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | - | - | 2026-03-01T09:00:00Z |
| createdBy | Actor who performed the action. | String | - | Yes | No | type:actor | - | - | user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| updatedBy | Present for schema consistency; equals createdBy in practice, since the row is never updated. | String | - | Yes | No | type:actor | - | - | user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
No deletedAt column — this table uses the plain auditFields mixin, not auditFieldsWithSoftDelete; unlike most other EM3 entities, it has no soft-delete state at all.
Audited fields
Recorded on created only — no update or delete method exists on ReadingActionLogRepositoryPort (insert, findPageByReading only): readingId, readAt, gaugeId, action, valueBefore, valueAfter, note, importId.
Excluded: none — no secret, no blob; note is a plain field a user entered deliberately, audited not redacted.
Every entry carries subjectEntityType = reading / subjectEntityId = readingId — readingActionLog does not self-subject, even though it is itself a domain-specific version store: the generic concept's own rule is that already having a history of its own is not a reason to exempt an entity from audit adoption. This makes "everything that happened to reading X" — its own audit trail plus every action logged about it — one subject-pinned read. Not registered for entityName resolution, and has no independent human-readable attribute to register.