standard_audit 0.9.0 → 0.10.0

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: a1d506a4bb5ad3811dd4a70b6be817374ec9a88ced8d1ffd9415bfddd28cc50b
4
- data.tar.gz: 52c8aaa70f86810c6b308c86fa81e10c368ab5ddb011d16f422745e7681c88da
3
+ metadata.gz: 7fca49bbca23bc24c89aa7249f0ea4718c2f4fa6bae03748ec93a45df17c4bd0
4
+ data.tar.gz: 7920932fea8af270498c7620d7cb4e18e0cfaab1c0deb7f299715ce578882dd2
5
5
  SHA512:
6
- metadata.gz: 2db9ee0ef4983ce2bd9a94d195bbbf973016f32a3e9e2a53b45476df6c9cc6d30f7edd2bfb7e9442d6839e05cc491263b91bb644462946510740c187eb861e29
7
- data.tar.gz: cce577cbc3f1b5829429ab30765c451f4a0fdca18b9f6283f075e1b3487b3fb763ceaeca846e39768c53707361bfc87024da65e0f8e662885a47766f814dd5c1
6
+ metadata.gz: 7117529ed69a81588ea18b081e3b8a8047dc9441fe116bef9e594beacc692108a1c8d625e2d4c1a959ac01a1d03857d9d94e91a4dfe65c52c35ce894b980e797
7
+ data.tar.gz: bdb36b281136e0928c4bac601bbee408de0cf040d70222c8d3339153c53312e1bd1820f136488086184426f68ce9f930b75d521b75fd25950f4f7b3bb232c66d
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.0] - 2026-07-31
11
+
12
+ ### Added
13
+
14
+ - **`config.audit_error_context_key`** (default `:audit_action`) — renames the `Rails.error.report` context key naming the audit action, without needing a whole handler. Two apps had overridden `audit_write_error_handler` for nothing but this: both tag every OTHER audit-error report site with `audit_event:` (four sites in one, twelve across ten files in the other), so adopting the gem name left the operations layer as the only place forking the convention. A handler written to rename one key also silently opts out of every future improvement to the built-in reporter.
15
+
16
+ ### Changed
17
+
18
+ - **The built-in reporter no longer fires when `raise_on_audit_write_error` re-raises.** The caller receives the error and owns it, so reporting as well produced two events for one failure in every host that both sets the flag and reports on what it catches — which is most hosts that set it at all, since the flag exists for hosts treating an unaudited write as a failure worth handling. Two of the five apps hit this independently and each wrote a log-only handler to work around it. A host that does NOT catch the error still gets a report, via its framework unhandled-error path. An explicitly configured `audit_write_error_handler` is unaffected and still runs under either policy — only the built-in reporter steps aside.
19
+
20
+ ### Fixed
21
+
22
+ - **The write-site scan no longer reads comments as calls.** An operation declaring `audit_none!` that explained itself in prose naming `audit!` was flagged by `unexpected_write_sites` — a false failure, which hosts worked around by backticking the token in their own comments. Source is now lexed with `Ripper` and comment tokens dropped before scanning; a file that cannot be lexed falls back to raw source (the old behaviour). Lexing rather than stripping `#` to end-of-line, because the naive form eats `"#{interpolation}"` and `%w[#]` and would trade a false failure for a false pass.
23
+ - **Corrected a docstring that was wrong for half its own surface.** It claimed the source-scanning predicates "err towards a false pass rather than a false failure". That held only for `missing_write_sites`; in the `unexpected_write_sites` direction the same match produced a false failure. Worse than the bug itself, since it told anyone hitting the failure not to suspect the scanner.
24
+
10
25
  ## [0.9.0] - 2026-07-31
11
26
 
12
27
  ### Added
@@ -10,7 +10,8 @@ module StandardAudit
10
10
  :metadata_builder, :before_checksum_hooks,
11
11
  :anonymizable_metadata_keys, :retention_days,
12
12
  :audit_catalogue, :verify_audit_declarations,
13
- :raise_on_audit_write_error, :audit_write_error_handler
13
+ :raise_on_audit_write_error, :audit_write_error_handler,
14
+ :audit_error_context_key
14
15
 
15
16
  def initialize
16
17
  @subscriptions = []
@@ -124,6 +125,23 @@ module StandardAudit
124
125
  # write failure under either policy.
125
126
  @audit_write_error_handler = nil
126
127
 
128
+ # The `Rails.error.report` context key naming the audit action, for the
129
+ # built-in reporter. Exists because two apps independently overrode
130
+ # `audit_write_error_handler` for nothing but this key: both tag every
131
+ # OTHER audit-error report site with `audit_event:` (four sites in one
132
+ # app, twelve across ten files in the other), so adopting the gem's name
133
+ # left the operations layer as the only place forking the convention.
134
+ #
135
+ # That divergence fails silently and asymmetrically — a saved error-tracker
136
+ # search grouped on the host's key keeps working and simply stops
137
+ # containing operation write failures. Nothing errors, nothing goes red,
138
+ # and the search still looks healthy.
139
+ #
140
+ # A whole handler to rename one key is a lot of ceremony, and a handler
141
+ # written for that reason also silently opts out of every future
142
+ # improvement to the built-in reporter. Default keeps existing behaviour.
143
+ @audit_error_context_key = :audit_action
144
+
127
145
  # Retention defaults from ENV so it can be set per-environment without a
128
146
  # code change. Unset/blank/non-positive => nil (infinite retention, the
129
147
  # compliance-safe default that never auto-deletes). A host app can still
@@ -1,3 +1,5 @@
1
+ require "ripper"
2
+
1
3
  module StandardAudit
2
4
  module Operation
3
5
  # The registry, the catalogue resolver, the write-error policy, and the
@@ -19,10 +21,20 @@ module StandardAudit
19
21
  #
20
22
  # The scan is FILE-scoped, not class-scoped — two operations defined in
21
23
  # one file share a verdict. Zeitwerk requires one class per file in a
22
- # real app, so this only matters for fixtures. A commented-out or
23
- # documented `audit!` in the same file also counts as a write site; the
24
- # predicates deliberately err towards a false pass rather than a false
25
- # failure, since the runtime guard is the authoritative check.
24
+ # real app, so this only matters for fixtures.
25
+ #
26
+ # Comments are stripped before scanning (see .strip_comments), so a
27
+ # documented or commented-out `audit!` is not a write site. Until 0.9.1
28
+ # it was, and the docstring here claimed the predicates "err towards a
29
+ # false pass rather than a false failure" — which was only ever true of
30
+ # {.missing_write_sites}. In the {.unexpected_write_sites} direction the
31
+ # same match produces a false FAILURE, so an `audit_none!` class that
32
+ # explained itself in prose failed the check. The claim was wrong for
33
+ # half its own surface, which is worse than the bug: it told anyone
34
+ # hitting the failure not to suspect the scanner.
35
+ #
36
+ # What remains genuinely heuristic: a string literal containing `audit!`
37
+ # still counts, and the runtime guard is still the authoritative check.
26
38
  WRITE_SITE_PATTERN = /(?<![\w.:])audit!\s*(?:\(|["':@$\w])/
27
39
 
28
40
  class << self
@@ -118,15 +130,24 @@ module StandardAudit
118
130
  # @raise [StandardError] the original error when
119
131
  # `config.raise_on_audit_write_error` is true
120
132
  def handle_write_error(error, action:, operation:)
133
+ raising = StandardAudit.config.raise_on_audit_write_error
121
134
  handler = StandardAudit.config.audit_write_error_handler
122
135
 
123
136
  if handler
124
137
  handler.call(error, action: action, operation: operation)
125
- else
138
+ elsif !raising
139
+ # Deliberately NOT reported when re-raising: the caller receives the
140
+ # error and owns it from here. Reporting as well produced two events
141
+ # for one failure in every host that both sets
142
+ # `raise_on_audit_write_error` and reports on the error it catches —
143
+ # which is most hosts that set the flag at all, since the flag exists
144
+ # for hosts that treat an unaudited write as a failure worth
145
+ # handling. And a host that does NOT catch it still gets the report,
146
+ # via its framework's own unhandled-error path.
126
147
  report_write_error(error, action: action, operation: operation)
127
148
  end
128
149
 
129
- raise error if StandardAudit.config.raise_on_audit_write_error
150
+ raise error if raising
130
151
 
131
152
  nil
132
153
  end
@@ -228,7 +249,29 @@ module StandardAudit
228
249
  path = source_path(klass)
229
250
  return nil unless path && File.exist?(path)
230
251
 
231
- File.read(path)
252
+ strip_comments(File.read(path))
253
+ end
254
+
255
+ # Comments are not write sites. Scanning raw source made a class that
256
+ # declares `audit_none!` and *explains why* in prose ("No direct audit!
257
+ # here") fail `unexpected_write_sites` — the scanner matched the word in
258
+ # the comment. That is a false failure, and it forced hosts to backtick
259
+ # the token in their own comments to appease a static check.
260
+ #
261
+ # Lexing rather than regex-stripping `#` to end-of-line, because the
262
+ # naive form eats `"#{interpolation}"` and `%w[#]`, which would trade a
263
+ # false failure for a false pass. A file Ripper cannot lex (syntax the
264
+ # running Ruby doesn't accept) falls back to the raw source: the old
265
+ # behaviour, which is conservative in the `audits` direction.
266
+ def strip_comments(src)
267
+ tokens = Ripper.lex(src)
268
+ return src if tokens.nil?
269
+
270
+ tokens.reject { |(_pos, type, _tok, _state)| type == :on_comment }
271
+ .map { |(_pos, _type, tok, _state)| tok }
272
+ .join
273
+ rescue StandardError
274
+ src
232
275
  end
233
276
 
234
277
  def subclassed?(klass)
@@ -244,7 +287,8 @@ module StandardAudit
244
287
  Rails.error.report(
245
288
  error,
246
289
  handled: true,
247
- context: { audit_action: action, operation: operation.class.name }
290
+ context: { StandardAudit.config.audit_error_context_key => action,
291
+ operation: operation.class.name }
248
292
  )
249
293
  end
250
294
  end
@@ -1,3 +1,3 @@
1
1
  module StandardAudit
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
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.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim