standard_audit 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80719c54fd8a1d3859f52741cfc4a478ec670114ad5ab025b1b9b14a10a80625
4
- data.tar.gz: d5baa91d4e29844e0e0c375e0ef6c9b3cd4f2d6eba0284696911891270f3befa
3
+ metadata.gz: a6da3bdedaecde5d44a57afe47372bf419b9290ace08a2312d1c522df5a67561
4
+ data.tar.gz: 283e8c2839ddce46fa2ec6468423c75380256f51f05a0a52844763d5454b7da3
5
5
  SHA512:
6
- metadata.gz: 3fc6d5267408dd0858b0cecac9e449c00b6ef6e75b12cb9cb74167634769cd09f95fa51805a948f9a20e100b7bbd43237f473f4508d211a8c66293c0af2bbe3f
7
- data.tar.gz: cdb5c7130f54d124da4e2b02586e510b5a75693c729d30d495403d4e948b7d6fcc81b640cb241a138292d452ebb5a52f0d84a6a0f178e8ecb982d7a209c179ea
6
+ metadata.gz: 96cd2995bbaaab665bc485770604f37b0b4188cf052a30639b804882a70cc276fad7e10b7ee21c65fa0294018687e06cea921ae7e387f4666a3f7298d29bbd09
7
+ data.tar.gz: 810d2becd35d3e0a5325786b86415c48318b4c23d7c8df46742916053cff6eaf5f97e3cba1e94b8ab479437751ba94177c28b05bc4c12837208a975698788aa0
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.1] - 2026-09-24
11
+
12
+ ### Upgrade steps
13
+
14
+ 1. **If you installed the 0.12.0 `add_anonymized_at` migration, check your copy.** The 0.12.0 template put `return if column_exists?(:audit_logs, :anonymized_at)` inside `change`. That early return also runs on rollback, so `db:rollback` deleted the `schema_migrations` row and silently left the column in place. Replace the body with `add_column :audit_logs, :anonymized_at, :datetime, if_not_exists: true` (in `change`), or copy the new up/down template. Nothing to do if your copy already uses `if_not_exists:` (sidekick-web, jumpdrive-web) or if you never ran the generator.
15
+ 2. If an error-tracker search or alert matches `before_checksum` hook failures on the `audit_event` context key, see Fixed.
16
+
17
+ ### Fixed
18
+
19
+ - **The `add_anonymized_at` migration is reversible.** The template now uses explicit `up` / `down` with `add_column ..., if_not_exists: true` and `remove_column ..., if_exists: true`. It is still idempotent, and rollback now removes the column. A new generator spec runs the generated migration up, down and up.
20
+ - **`before_checksum` hook failures use `config.audit_error_context_key`.** The hook's `Rails.error.report` hard-coded `audit_event:` as its context key. Every other audit-error report site uses the configured key (default `:audit_action`). Apps that set `audit_error_context_key = :audit_event` see no change. Apps on the default now get `audit_action:` for hook failures too, matching every other audit error.
21
+
22
+ ### Documentation
23
+
24
+ - `current_scope_resolver` is for scope derived from `Current`. Scope derived from the row (e.g. the target's organisation) belongs in a `before_checksum` or `before_write` hook. The README no longer describes sidekick-web's target-derived hook as a `Current` back-fill.
25
+ - New note: `before_write` / `before_checksum` run once per row, batched writes included. Memoize per-actor lookups (e.g. in a `CurrentAttributes` cache) to avoid an N+1 on a batch flush.
26
+ - `record(raise: false)` reports through `Rails.error`, so failures reach Sentry only if a `Rails.error` subscriber is registered (`sentry-rails` registers one).
27
+
10
28
  ## [0.12.0] - 2026-09-24
11
29
 
12
30
  ### Upgrade steps
data/README.md CHANGED
@@ -116,6 +116,12 @@ StandardAudit.record("auth.token_invalid",
116
116
  The default is `raise: true` (unchanged). In block form the option only
117
117
  governs the audit write; errors from your block always propagate.
118
118
 
119
+ `raise: false` does not talk to Sentry (or any other tracker) itself. It calls
120
+ `Rails.error.report`, so a swallowed failure reaches your error tracker only if
121
+ something subscribes to `Rails.error`. `sentry-rails` registers that subscriber
122
+ for you. With a hand-rolled Sentry setup, register one
123
+ (`Rails.error.subscribe(...)`), or these failures show up only in the log.
124
+
119
125
  **Replace your host code with** `raise: false`. It supersedes the
120
126
  `AuditAuthFailure#record_auth_failure` rescue-and-report wrapper
121
127
  (sidekick-web, luminality-web, nutripod-web
@@ -179,6 +185,27 @@ dereferencing and `sensitive_keys` redaction, so anything it injects is still
179
185
  filtered. Mutate `entry` in place; the return value is ignored. Raising aborts
180
186
  the write: direct callers see the error, the subscribers rescue and report it.
181
187
 
188
+ **Hooks run once per row, batched writes included.** `before_write` and
189
+ `before_checksum` run for every row inside `StandardAudit.batch` too (at flush
190
+ time for `before_checksum`). A hook that looks something up per actor (a role,
191
+ a membership, a tenant) therefore runs one query per row and turns a batch into
192
+ an N+1. Memoize those lookups for the unit of work, for example in a
193
+ `CurrentAttributes` cache that resets with the request or job:
194
+
195
+ ```ruby
196
+ class Current < ActiveSupport::CurrentAttributes
197
+ attribute :audit_actor_roles
198
+
199
+ def self.audit_role_for(actor)
200
+ self.audit_actor_roles ||= {}
201
+ audit_actor_roles[actor.to_global_id.to_s] ||= actor.audit_role
202
+ end
203
+ end
204
+
205
+ # actor_role: a column your app added to audit_logs
206
+ config.before_checksum { |log| log.actor_role = Current.audit_role_for(log.actor) if log.actor }
207
+ ```
208
+
182
209
  **Replace your host code with** a `before_write`. It supersedes:
183
210
 
184
211
  - a hand-built job-side wrapper that runs a guard before `StandardAudit.record`
@@ -641,14 +668,25 @@ It is a *fallback*: an explicit `scope:` and a scope found by `scope_extractor`
641
668
  always win. It applies on every write path (direct `record`, `audit!`,
642
669
  `record_audit`, both subscribers; sync, async and batched). Default `nil`.
643
670
 
644
- **Replace your host code with** the one line above. It supersedes:
671
+ `current_scope_resolver` is for scope that comes from **ambient request
672
+ state** (`Current`). It takes no arguments and never sees the row. Scope that
673
+ derives from the **row itself**, such as the organisation that owns the
674
+ target, belongs in a `before_checksum` hook (or `before_write`), which receives
675
+ the record or entry:
676
+
677
+ ```ruby
678
+ config.before_checksum do |log|
679
+ log.scope ||= log.target.organisation if log.target.respond_to?(:organisation)
680
+ end
681
+ ```
645
682
 
646
- - a `scope_extractor` that falls back to `Current` (nutripod-web:
647
- `->(payload) { payload[:scope] || Current.channel || Current.organisation }`),
648
- which only ever covered the subscriber path — keep `scope_extractor` for
649
- reading the payload, move the `Current` fallback here;
650
- - a `before_checksum` hook that back-fills `log.scope` from `Current`
651
- (sidekick-web), which before 0.12.0 never ran on the batched path.
683
+ **Replace your host code with** the one line above when your fallback reads
684
+ `Current`. It supersedes a `scope_extractor` that falls back to `Current`
685
+ (nutripod-web:
686
+ `->(payload) { payload[:scope] || Current.channel || Current.organisation }`),
687
+ which only ever covered the subscriber path. Keep `scope_extractor` for reading
688
+ the payload and move the `Current` fallback here. Target-derived scope hooks
689
+ stay as they are; since 0.12.0 they also run on the batched path.
652
690
 
653
691
  ## Async Processing
654
692
 
@@ -597,7 +597,13 @@ module StandardAudit
597
597
  # persist — so the hook would not actually be "skipped".
598
598
  restore_attributes_from(snapshot)
599
599
  Rails.logger.warn("[StandardAudit] before_checksum hook failed: #{e.class}: #{e.message}")
600
- Rails.error.report(e, handled: true, context: { audit_event: event_type }) if Rails.respond_to?(:error)
600
+ if Rails.respond_to?(:error) && Rails.error
601
+ Rails.error.report(
602
+ e,
603
+ handled: true,
604
+ context: { StandardAudit.config.audit_error_context_key => event_type }
605
+ )
606
+ end
601
607
  nil
602
608
  end
603
609
  end
@@ -1,15 +1,25 @@
1
1
  class AddAnonymizedAtToAuditLogs < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
- def change
3
- # A nullable column with no default: a metadata-only change on PostgreSQL,
4
- # no table rewrite, nothing for StrongMigrations to object to. Idempotent,
5
- # so it is safe on a host that already has the column (a 0.12+ install).
6
- #
7
- # Rows anonymized BEFORE this migration keep a NULL stamp and still fail
8
- # `verify_chain` as `digest_mismatch`; there is no way to tell them apart
9
- # from tampered rows after the fact. If your erasure records identify them,
10
- # stamp them by hand (`update_columns(anonymized_at: ...)`).
11
- return if column_exists?(:audit_logs, :anonymized_at)
2
+ # A nullable column with no default: a metadata-only change on PostgreSQL,
3
+ # no table rewrite, nothing for StrongMigrations to object to. Idempotent,
4
+ # so it is safe on a host that already has the column (a 0.12+ install).
5
+ #
6
+ # Rows anonymized BEFORE this migration keep a NULL stamp and still fail
7
+ # `verify_chain` as `digest_mismatch`; there is no way to tell them apart
8
+ # from tampered rows after the fact. If your erasure records identify them,
9
+ # stamp them by hand (`update_columns(anonymized_at: ...)`).
10
+ #
11
+ # Explicit up/down with `if_not_exists:` / `if_exists:` rather than an early
12
+ # `return if column_exists?` inside `change`: that return also runs on
13
+ # rollback, which then deletes the schema_migrations row and silently leaves
14
+ # the column behind.
15
+ #
16
+ # Rolling back drops the column even if it came from your create_audit_logs
17
+ # migration (0.12+ installs). Those installs do not need this migration.
18
+ def up
19
+ add_column :audit_logs, :anonymized_at, :datetime, if_not_exists: true
20
+ end
12
21
 
13
- add_column :audit_logs, :anonymized_at, :datetime
22
+ def down
23
+ remove_column :audit_logs, :anonymized_at, if_exists: true
14
24
  end
15
25
  end
@@ -1,3 +1,3 @@
1
1
  module StandardAudit
2
- VERSION = "0.12.0"
2
+ VERSION = "0.12.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_audit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim