Skip to content

Unified change history table #336

Closed
Closed

Description

@stakach

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_redact macros, history_correlation_id / history_resource_id / history_record_as overrides per model
  • Captures changes at persist time (pg-orm on_persist hook, after all before_* callbacks and validation), written in the same transaction; no entry when only ignored fields changed
  • Secrets redacted from changes and snapshot
  • #change_history, .change_history(id), History.correlated(id), .undelete(id) (#history clashes with the Booking history attribute) (restores latest delete snapshot with original id, writes a restore entry; 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; drop GroupHistory + group_history (rows migrated)
  • Playlist::Revision: approve entry only, snapshot of approved revision, correlation = playlist id
  • SignageTemplate: approve entry on approve_draft!, drafts not recorded
  • EventMetadata: resource id = event_id
  • BookingInstance: correlation = booking id
  • Metadata/Settings versioning unchanged

Tasks

active-model

  • Add changes ({field => {old, new}}), converter-aware changes_json and attributes_json helpers, specs, release tags v4.6.0 / v4.7.0

pg-orm

models — #337, #338 (metadata provider)

  • Bump active-model / pg-orm
  • Migration: rebuild history, migrate group_history rows, drop group_history
  • History model, History::Metadata, fiber-scoped context
  • History::Tracked mixin: 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 GroupHistory and its mixin; migrate existing History writers
  • Specs for all of the above, ./test green

rest-api — PlaceOS/rest-api#456

  • history_metadata(request, current_user) + around_action
  • GET /history with filters (resource types include/exclude, resource type + id, correlation id, user)
  • Repoint /group_history to History.correlated
  • Undelete endpoint (admin)
  • Point back to released models

ts-client

  • History types + query functions, group history types updated

Activity

  1. stakach commented on Oct 19, 2026

    @stakach
    MemberAuthor

    Done: models #337/#338 (v9.122.0), PlaceOS/rest-api#456, ts-client d2b24fe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions