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