Repository navigation
Unified change history table #336
Description
Unified change history: a single history table filterable by resource type, resource id or correlation id, sorted by created_at. Replaces GroupHistory; Booking::History (JSONB column) stays as is.
Design
Table history (rebuilt; current table is write-only, no readers)
| column | type | notes |
|---|---|---|
id |
UUID PK DEFAULT uuidv7() |
|
resource_type |
TEXT |
table name |
resource_id |
TEXT |
row id or static reference (event_id, "group_id:user_id" for composite keys) |
correlation_id |
TEXT NULL |
parent: group id, playlist id, booking id |
action |
TEXT |
create / update / delete / approve / restore |
changed_fields |
TEXT[] |
|
diff |
JSONB |
{field: [old, new]} (create: old = null) |
snapshot |
JSONB NULL |
full record on delete (undelete) and approve |
user_id, user_email |
TEXT NULL |
no FK, survives user deletion |
context |
JSONB |
app id/name/date/build, user agent, ip, request id, api key |
created_at |
TIMESTAMPTZ |
Indexes: (resource_type, resource_id, created_at DESC), (correlation_id, created_at DESC), (user_id), (created_at).
Mixin History::Tracked
history_ignore/history_redactmacros,history_correlation_id/history_resource_id/history_record_asoverrides per model- Captures changes at persist time (pg-orm
on_persisthook, after allbefore_*callbacks and validation), written in the same transaction; no entry when only ignored fields changed - Secrets redacted from
changesandsnapshot #change_history,.change_history(id),History.correlated(id),.undelete(id)(#historyclashes with the Bookinghistoryattribute) (restores latest delete snapshot with original id, writes arestoreentry; cascaded children not restored)
Request context: History::Metadata set per instance or fiber-scoped via History.with_metadata(meta) { } so cascaded saves are attributed. rest-api provides history_metadata(request, current_user) via around_action.
Retention (PLACE_HISTORY_RETENTION_DAYS=30, PLACE_HISTORY_MAX_ENTRIES=250, PLACE_HISTORY_MIN_ENTRIES=5): per resource keep entries within 30 days capped at 250, never fewer than the newest 5; deleted resources lose all entries once the delete is older than 30 days. Per-resource trim on write; rate-limited batched History.cleanup_expired.
Special cases
- Group models:
correlation_id= group id; dropGroupHistory+group_history(rows migrated) Playlist::Revision:approveentry only, snapshot of approved revision, correlation = playlist idSignageTemplate:approveentry onapprove_draft!, drafts not recordedEventMetadata: resource id =event_idBookingInstance: correlation = booking id- Metadata/Settings versioning unchanged
Tasks
active-model
- Add
changes({field => {old, new}}), converter-awarechanges_jsonandattributes_jsonhelpers, specs, release tags v4.6.0 / v4.7.0
pg-orm
- Non-breaking
on_persisthook (inside transaction, after callbacks + validation), specs, release tag — feat: add on_persist hook spider-gazelle/pg-orm#23 (v2.5.0), aggregates ignore ORDER BY fix: ignore ordering when calculating aggregates spider-gazelle/pg-orm#24 (v2.5.1)
models — #337, #338 (metadata provider)
- Bump active-model / pg-orm
- Migration: rebuild
history, migrategroup_historyrows, dropgroup_history -
Historymodel,History::Metadata, fiber-scoped context -
History::Trackedmixin: capture, redaction, ignore list, correlation, query helpers - Delete entries +
undelete - Retention: per-resource trim,
cleanup_expired, ENV overrides - Include in: Zone, ControlSystem, Module, Driver, Repository, Trigger, TriggerInstance, User, Authority, LDAP/OAuth/SAML strategies, DoorkeeperApplication, Asset, AssetType, AssetCategory, AssetPurchaseOrder, Alert, AlertDashboard, Playlist, Playlist::Item, SignageTemplate, Group, GroupUser, GroupZone, GroupInvitation, EventMetadata, Booking, BookingInstance
- Playlist::Revision and SignageTemplate approval entries
- Remove
GroupHistoryand its mixin; migrate existingHistorywriters - Specs for all of the above,
./testgreen
rest-api — PlaceOS/rest-api#456
-
history_metadata(request, current_user)+around_action -
GET /historywith filters (resource types include/exclude, resource type + id, correlation id, user) - Repoint
/group_historytoHistory.correlated - Undelete endpoint (admin)
- Point back to released models
ts-client
- History types + query functions, group history types updated
Activity
stakach commentedon Oct 19, 2026
Done: models #337/#338 (v9.122.0), PlaceOS/rest-api#456, ts-client d2b24fe.