Threadline.Audit (Threadline v1.0.0)

Copy Markdown View Source

Threadline.Audit runs a domain write inside one database transaction and optionally links the write to a semantic action.

Call from Phoenix context modules (or Oban workers) after Threadline.Plug / Threadline.Job have resolved %Threadline.Semantics.AuditContext{} and %Threadline.Semantics.ActorRef{}. The helper does not replace those edge modules.

Options

  • :actor_ref — %Threadline.Semantics.ActorRef{} (required unless audit_context: or allow_missing_actor: true on capture-only paths)
  • :audit_context — %Threadline.Semantics.AuditContext{}; supplies actor_ref, correlation_id, and request_id (top-level opts override)
  • :action — atom or {:name, action_opts} for Threadline.record_action/2 after the callback succeeds; links audit_transactions.action_id via txid_current() (correlation-ready path)
  • :capture_only — same as omitting :action (capture-only path)
  • :allow_missing_actor — when true and :action is absent, permits nil actor_ref (non-recommended for multi-tenant SaaS)
  • :transaction_meta — map stored on audit_transactions.meta on both correlation-ready (:action present) and capture-only paths
  • :correlation_id, :request_id, :job_id — forwarded to record_action/2 when :action is an atom

Capture-only vs correlation-ready

:actionStrict :correlation_id timeline/export
presentcorrelation-ready — action_id linked
absentcapture-only — strict filters won't match

See Threadline.timeline/2 for strict :correlation_id semantics.

Callback contract

Pass fn -> domain_result end with Repo captured in the closure.

Allowed: domain inserts/updates/deletes; Repo.rollback/1 on failure.

Forbidden inside the callback: set_config for threadline.actor_ref, Threadline.record_action/2, and nested Repo.transaction/1 (breaks GUC and txid_current() linkage).

Example

Threadline.Audit.transaction(MyApp.Repo, [
  actor_ref: actor_ref,
  action: :post_created,
  transaction_meta: %{"organization_id" => organization_id}
], fn ->
  MyApp.Repo.insert!(Post.changeset(%Post{}, attrs))
end)

Return shape

Returns {:ok, result} | {:error, reason}. result is the callback result, treated as caller-owned and opaque to Threadline. On success, it is the callback return with :audit_transaction_id merged when capture produced an audit_transactions row (map callback) or wrapped as %{result: value, audit_transaction_id: id} for non-map returns. On failure, {:error, reason} (:missing_actor, :missing_audit_transaction_for_link, changesets, or rollback reasons).

Errors

  • {:error, :missing_actor} — nil actor_ref when required
  • {:error, :missing_audit_transaction_for_link} — linkage update_all count != 1

Summary

Types

An action name passed to Threadline.record_action/2, optionally paired with its typed options.

An option accepted by transaction/3.

Functions

Runs fun inside repo.transaction/1 after setting the transaction-local threadline.actor_ref GUC and optionally recording a semantic action, then returns {:ok, result} or {:error, reason}. On success, result may include an :audit_transaction_id when capture creates an audit_transactions row: Threadline merges the id into map results and wraps non-map results as %{result: value, audit_transaction_id: id}.

Types

action_opt()

@type action_opt() :: atom() | {atom(), [Threadline.record_action_opt()]}

An action name passed to Threadline.record_action/2, optionally paired with its typed options.

transaction_opt()

@type transaction_opt() ::
  {:actor_ref, Threadline.Semantics.ActorRef.t() | nil}
  | {:audit_context, Threadline.Semantics.AuditContext.t() | nil}
  | {:action, action_opt() | nil}
  | {:capture_only, boolean()}
  | {:allow_missing_actor, boolean()}
  | {:transaction_meta, Threadline.json_map() | nil}
  | {:correlation_id, String.t() | nil}
  | {:request_id, String.t() | nil}
  | {:job_id, String.t() | nil}
  | Threadline.storage_schema_opt()

An option accepted by transaction/3.

Functions

transaction(repo, opts, fun)

@spec transaction(module(), [transaction_opt()], (-> result)) ::
  {:ok, result} | {:error, term()}
when result: term()

Runs fun inside repo.transaction/1 after setting the transaction-local threadline.actor_ref GUC and optionally recording a semantic action, then returns {:ok, result} or {:error, reason}. On success, result may include an :audit_transaction_id when capture creates an audit_transactions row: Threadline merges the id into map results and wraps non-map results as %{result: value, audit_transaction_id: id}.

See module doc for options, callback rules, and return envelope.