Skip to content

Restore semantic User auth events on the PaperTrail timelineΒ #2249

Description

@maebeale

πŸ€– From Claude:

Background

We're moving the activity timeline onto PaperTrail (see PR #2245). PaperTrail versions User account changes, but three of Ahoy's semantic auth.* events don't survive the move β€” they change skipped columns or bypass PaperTrail entirely, so no version is written at all:

Ahoy event Trigger Why no version
auth.password_changed encrypted_password change encrypted_password is in has_paper_trail skip: (secret)
auth.password_first_set encrypted_password (welcome flow) same skip
auth.account_setup_completed welcome_instructions_token cleared done via update_columns β€” bypasses callbacks/PaperTrail

Separately, the column-backed auth events (login, admin granted/revoked, locked/unlocked, deactivated/reactivated, email changed/confirmed, reset sent, welcome sent, account deleted) do produce versions β€” but as raw column diffs (super_user: false β†’ true), not the semantic label Ahoy emitted.

Do we have the timestamps we'd need? No.

Present *_at columns cover most events, but two are missing:

  • password_changed_at β€” needed for password_changed / password_first_set
  • setup_completed_at (or welcome_completed_at) β€” needed for account_setup_completed

Proposed plan

  1. Add non-secret timestamp columns password_changed_at and setup_completed_at, stamped whenever those events happen. Stamping them creates a real (non-secret) version, restoring the three lost events. Fix the update_columns bypass so the setup-completion write goes through a normal update (or explicitly stamp the timestamp).
  2. Semantic labels for the timeline. Two options β€” decide in this issue:
    • (a) Derive in the timeline presenter from the version's changeset (e.g. super_user trueβ†’false β‡’ "Admin revoked"). No schema change; labeling logic lives in one place with the rest of the timeline rendering.
    • (b) action_note meta column on versions, populated at write time via has_paper_trail meta: (a proc that names the semantic event). Captures intent the diff can't always recover, but duplicates labeling logic and only helps User.
    • Recommendation: (a) for the column-backed events (the diff already says what happened), plus the new timestamps from step 1 for the three bypass cases. Reserve action_note for cases where intent genuinely isn't recoverable from the diff.
    • password_first_set vs password_changed is still derivable under (a): the password_changed_at transition disambiguates them β€” nil β†’ T is first-set, T β†’ T' is a change β€” so this pair doesn't need action_note.
  3. Attribute the actor (whodunnit) on self-serve Devise flows. The lost/column-backed events fire in controllers that run without a logged-in user β€” PasswordsController and WelcomeController both skip_before_action :authenticate_user! (and confirmation/unlock link flows are the same). set_paper_trail_whodunnit reads current_user, so every version these flows write lands with nil whodunnit and the timeline's "By" column comes up blank β€” where Ahoy attributed the actor. Set PaperTrail.request.whodunnit to the resource itself (self-serve) in these actions, mirroring the existing updated_by: current_user || @user pattern already used in welcome_controller.rb. Without this, restoring the events isn't enough β€” they'd read as "by (nobody)".
  4. Keep the User skip: list as-is (secrets stay out of the trail); the new timestamps are the non-secret signal.

Out of scope

  • Attachment add/remove (avatar/logo) β€” separate residual gap, tracked with the broader attachment question.
  • Analytics vs. audit: PaperTrail replaces the account change timeline only. Ahoy's page views / prints / downloads / search intent / visits, and its association + rich-text change capture, have no version equivalent and stay on Ahoy. "Retire Ahoy" is scoped to the change-audit timeline, not engagement analytics.
  • This is deliberately a follow-up to JM: Person-scoped PaperTrail version trailΒ #2245, not part of it.

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