# darynongera/fort_audit architecture

> Architecture diagram of the GitHub repository darynongera/fort_audit, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-09-24). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/darynongera/fort_audit
- Repository: https://github.com/darynongera/fort_audit

## Overview

Fort is an Elixir audit-logging library for host applications. The main workflow wraps business Ecto.Multi operations with an audit step, persists the business changes and audit row atomically, then emits the committed row through Logger and stamps it as emitted. It also supports standalone and best-effort logging, changeset-derived audit data, and recovery of persisted-but-unemitted rows. The README documents these paths; some transaction and reconciliation internals were not included in the sampled source, so only documented wiring is shown.

Main entry points: [`lib/fort/audit.ex`](https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex) (Atomic transaction), [`lib/fort/emitter.ex`](https://github.com/darynongera/fort_audit/blob/main/lib/fort/emitter.ex) (Logger emitter), [`lib/fort/schemas/audit_log.ex`](https://github.com/darynongera/fort_audit/blob/main/lib/fort/schemas/audit_log.ex) (Audit log schema), [`lib/mix/tasks/fort/reconcile.ex`](https://github.com/darynongera/fort_audit/blob/main/lib/mix/tasks/fort/reconcile.ex) (Reconcile task)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_transaction["Audit workflows"]
  node_audit_api["Audit API<br/>[audit.ex]"]
  node_audited_multi["Audited Multi<br/>[audited_multi.ex]"]
  node_missing_step["Missing-step guard"]
  node_atomic_path["Atomic transaction<br/>[audit.ex]"]
  node_best_effort["Best-effort logging<br/>[audit.ex]"]
  node_standalone["Standalone logging<br/>[audit.ex]"]
  node_changeset_data["Changeset derivation<br/>[audit.ex]"]
end

subgraph group_storage["Audit persistence"]
  node_repo[("Configured Ecto repo")]
  node_audit_schema["Audit log schema<br/>[audit_log.ex]"]
end

subgraph group_emission["Emission and recovery"]
  node_emitter["Logger emitter<br/>[emitter.ex]"]
  node_reconcile_api["Reconciliation API<br/>[audit.ex]"]
  node_reconcile_task["Reconcile task<br/>[reconcile.ex]"]
end

node_host(("Host application"))
node_logger["Elixir Logger"]

node_host -->|"calls"| node_audit_api
node_audit_api -->|"wraps"| node_audited_multi
node_audit_api -->|"runs"| node_atomic_path
node_atomic_path -->|"raises on omission"| node_missing_step
node_atomic_path -->|"builds audit row"| node_audit_schema
node_atomic_path -->|"transacts"| node_repo
node_atomic_path -->|"emits committed rows"| node_emitter
node_host -->|"calls"| node_best_effort
node_best_effort -->|"builds audit row"| node_audit_schema
node_best_effort -->|"inserts row"| node_repo
node_host -->|"calls"| node_standalone
node_standalone -->|"builds audit row"| node_audit_schema
node_standalone -->|"inserts row"| node_repo
node_standalone -->|"emits row"| node_emitter
node_host -->|"derives audit data"| node_changeset_data
node_emitter -->|"logs structured event"| node_logger
node_emitter -->|"stamps emitted time"| node_repo
node_reconcile_task -->|"invokes"| node_reconcile_api
node_reconcile_api -->|"queries unemitted rows"| node_repo
node_reconcile_api -->|"re-emits rows"| node_emitter

click node_audit_api "https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex"
click node_audited_multi "https://github.com/darynongera/fort_audit/blob/main/lib/fort/audited_multi.ex"
click node_missing_step "https://github.com/darynongera/fort_audit/blob/main/lib/fort/missing_audit_step_error.ex"
click node_atomic_path "https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex"
click node_best_effort "https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex"
click node_standalone "https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex"
click node_changeset_data "https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex"
click node_audit_schema "https://github.com/darynongera/fort_audit/blob/main/lib/fort/schemas/audit_log.ex"
click node_emitter "https://github.com/darynongera/fort_audit/blob/main/lib/fort/emitter.ex"
click node_reconcile_api "https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex"
click node_reconcile_task "https://github.com/darynongera/fort_audit/blob/main/lib/mix/tasks/fort/reconcile.ex"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_audit_api,node_audited_multi,node_missing_step,node_atomic_path,node_best_effort,node_standalone,node_changeset_data toneBlue
class node_repo,node_audit_schema toneAmber
class node_emitter,node_reconcile_api,node_reconcile_task toneMint
class node_host,node_logger toneIndigo
```

## Components

### Audit workflows

- [Audit API](https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex): `lib/fort/audit.ex`
- [Audited Multi](https://github.com/darynongera/fort_audit/blob/main/lib/fort/audited_multi.ex): `lib/fort/audited_multi.ex`
- [Missing-step guard](https://github.com/darynongera/fort_audit/blob/main/lib/fort/missing_audit_step_error.ex): `lib/fort/missing_audit_step_error.ex`
- [Atomic transaction](https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex): `lib/fort/audit.ex`
- [Best-effort logging](https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex): `lib/fort/audit.ex`
- [Standalone logging](https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex): `lib/fort/audit.ex`
- [Changeset derivation](https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex): `lib/fort/audit.ex`

### Audit persistence

- **Configured Ecto repo**
- [Audit log schema](https://github.com/darynongera/fort_audit/blob/main/lib/fort/schemas/audit_log.ex): `lib/fort/schemas/audit_log.ex`

### Emission and recovery

- [Logger emitter](https://github.com/darynongera/fort_audit/blob/main/lib/fort/emitter.ex): `lib/fort/emitter.ex`
- [Reconciliation API](https://github.com/darynongera/fort_audit/blob/main/lib/fort/audit.ex): `lib/fort/audit.ex`
- [Reconcile task](https://github.com/darynongera/fort_audit/blob/main/lib/mix/tasks/fort/reconcile.ex): `lib/mix/tasks/fort/reconcile.ex`

### Other components

- **Host application**
- **Elixir Logger**

## Connections

- Host application → Audit API: calls
- Audit API → Audited Multi: wraps
- Audit API → Atomic transaction: runs
- Atomic transaction → Missing-step guard: raises on omission
- Atomic transaction → Audit log schema: builds audit row
- Atomic transaction → Configured Ecto repo: transacts
- Atomic transaction → Logger emitter: emits committed rows
- Host application → Best-effort logging: calls
- Best-effort logging → Audit log schema: builds audit row
- Best-effort logging → Configured Ecto repo: inserts row
- Host application → Standalone logging: calls
- Standalone logging → Audit log schema: builds audit row
- Standalone logging → Configured Ecto repo: inserts row
- Standalone logging → Logger emitter: emits row
- Host application → Changeset derivation: derives audit data
- Logger emitter → Elixir Logger: logs structured event
- Logger emitter → Configured Ecto repo: stamps emitted time
- Reconcile task → Reconciliation API: invokes
- Reconciliation API → Configured Ecto repo: queries unemitted rows
- Reconciliation API → Logger emitter: re-emits rows
