Module: StandardAudit::Operation

Extended by:
ActiveSupport::Concern
Defined in:
lib/standard_audit/operation.rb,
lib/standard_audit/operation/audit.rb

Overview

Operation-level audit contract, extracted from five independent copies of the same DSL (fundbright-web, jumpdrive-web, luminality-web, nutripod-web, sidekick-web).

It is a MODULE, never a base class. The five host ApplicationOperation classes range from 61 to 303 lines and diverge deliberately — one of them explicitly refuses a Result/execute lifecycle, and one app has no shared operation base at all. This module contributes the audit contract and nothing else: no lifecycle, no call, no Result.

It gives an including class:

- a class-level declaration of audit intent — `audits "x.y"` (it records
one or more audit events), `audit_none!` (it intentionally records
none), or `audit_abstract!` (it is a base/intermediate class, not a real
operation). The declaration is meant to be MANDATORY, enforced by the
host's meta-spec — see StandardAudit::Operation::Audit and
`standard_audit/rspec/operation`.

- a single private instance-level write path, `audit!(action, **attrs)`,
which verifies the declaration (dev/test only) and then writes through
StandardAudit.record.

── ADOPTION SHAPES ───────────────────────────────────────────────────────── Both shapes found in the estate work with no host configuration:

1. A shared base includes the module once, and the real operations are its
 subclasses. `inherited` registers them. The base itself is excluded
 from `Audit.operations` automatically because it declares nothing and
 has subclasses.

2. Every operation is a leaf that includes the module directly (no shared
 base). `included` registers each one.

── ERROR POLICY ──────────────────────────────────────────────────────────── DeclarationError ALWAYS propagates — it is a developer mistake caught in dev/test, and letting it decay into a swallowed write or a failure Result would hide drift from CI.

A genuine write failure is governed by config.raise_on_audit_write_error (default false, i.e. report and swallow). One of the five apps deliberately lets a failed audit write abort the operation, because for it an unaudited state change is a compliance failure; it sets the flag to true. A swallow-only module would have silently downgraded that posture, which is why this is configurable rather than fixed.

── CATALOGUE ─────────────────────────────────────────────────────────────── The gem has no knowledge of any host's action vocabulary. The host declares it:

StandardAudit.configure(baseline: true) do |config|
config.audit_catalogue = -> { AuditCatalogue::ACTIONS }
end

A CALLABLE, because referencing an autoloadable constant eagerly from an initializer pins the first-loaded copy and breaks Zeitwerk reloading. nil (the default) means the catalogue check is skipped entirely, so the DSL is adoptable before an app has a catalogue.

Membership is the ONLY rule applied to an action string. There is deliberately no dot-count, case, prefix, or namespace validation: one app's catalogue carries notification-bus names verbatim (jumpdrive-web.surface.audience_changed) because the subscriber records the bus name as-is, and normalising them would orphan historical rows.

Defined Under Namespace

Modules: Audit, ClassMethods Classes: DeclarationError