Module: ForestAdminAgent::AuditTrail::Recording

Included in:
ActionCapture, Capture, Snapshots
Defined in:
lib/forest_admin_agent/audit_trail/recording.rb

Overview

Shared by the two capture layers: Capture for the changes Forest writes, ActionCapture for the smart actions it runs. Holds the write protocol's vocabulary and its failure policy.

Constant Summary collapse

REDACTED =
'[redacted]'.freeze
PENDING =

A row is inserted before the write and confirmed after it. One left PENDING means the write may or may not have landed — that residue is evidence, and it is the point.

'pending'.freeze
DONE =
'done'.freeze
IDENTITY =
{ user_id: :id, user_first_name: :first_name,
user_last_name: :last_name, user_email: :email }.freeze

Instance Method Summary collapse

Instance Method Details

#audit_safelyObject

Everything after the pending insert is best-effort: by then the write has happened, so raising would report a failure for an operation that succeeded (and invite a retry that duplicates it). Losing the row is the lesser evil, so it is logged and dropped. Only the pending insert itself can refuse an operation, and only under critical: true — see ForestAdminAgent::AuditTrail.critical?.



49
50
51
52
53
54
# File 'lib/forest_admin_agent/audit_trail/recording.rb', line 49

def audit_safely
  yield
rescue StandardError => e
  AuditTrail.log_failure(e)
  nil
end

#correlation_key_for(caller) ⇒ Object

Same id for every change made within one request — set on the caller by the agent (see CallerParser), mirroring the Node agent's caller.requestId.

nil when the caller carries none, which is what a write outside any request looks like. Inventing one would group the row into a request of its own, indistinguishable from a genuine single-row request.



29
30
31
# File 'lib/forest_admin_agent/audit_trail/recording.rb', line 29

def correlation_key_for(caller)
  caller.respond_to?(:request_id) ? caller.request_id : nil
end

#identity_of(caller) ⇒ Object

Denormalised at write time, so the row says who acted then rather than whoever holds that id today. Read defensively: a caller built by another code path need not carry a full identity, and a NoMethodError here would refuse the write outright under critical: true.



20
21
22
# File 'lib/forest_admin_agent/audit_trail/recording.rb', line 20

def identity_of(caller)
  IDENTITY.transform_values { |reader| caller.respond_to?(reader) ? caller.public_send(reader) : nil }
end

#nowObject



41
42
43
# File 'lib/forest_admin_agent/audit_trail/recording.rb', line 41

def now
  Time.now.utc.iso8601(3)
end

#redact(values, redacted_fields) ⇒ Object



33
34
35
36
37
38
39
# File 'lib/forest_admin_agent/audit_trail/recording.rb', line 33

def redact(values, redacted_fields)
  return values if redacted_fields.empty?

  values.each_with_object({}) do |(field, value), result|
    result[field] = redacted_fields.include?(field) ? REDACTED : value
  end
end