The accounting pipeline is the defining mental model of Logistiki. Each stage is
represented in code (Logistiki.Accounting.Pipeline), recorded in the audit
trail, and emitted as telemetry.
Business Event
│
▼
Event Normalization
│
▼
Datalog Fact Generation
│
▼
Business Rule Evaluation
│
▼
Accounting Policy Selection
│
▼
Accounting Template Resolution
│
▼
Journal Builder
│
▼
Posting Builder
│
▼
Ledger Invariant Validation
│
▼
Ledger Backend Execution
│
▼
Projection Generation
│
▼
Audit EvidenceStages
1. Event normalization
Logistiki.Event.normalize/1 dispatches to the event struct's Logistiki.Event
implementation and returns a Logistiki.Event.Normalized struct — the canonical,
flattened representation the knowledge layer consumes.
2. Datalog fact generation
Logistiki.Knowledge.Facts.generate/1 turns the normalized event into a list of
{relation, values} facts (e.g. {:event_type, [:evt, :deposit_received]},
{:event_amount_cents, [:evt, 100000]}). The event is represented inside the
Datalog program by the fixed atom :evt.
3. Business rule evaluation
The knowledge program (Logistiki.Knowledge.Program) derives blocked(:evt) and
requires_approval(:evt). A blocked event short-circuits with
%Logistiki.Error{code: :blocked_event}; an event requiring approval
short-circuits with %Logistiki.Error{code: :approval_required}. Events with no
accounting impact produce a valid result with journal: nil.
4. Accounting policy selection
The program derives policy(:evt, policy_name) from the event's facts. Exactly
one policy must match; zero matches yields :no_policy_found, and ambiguity is
resolved deterministically (sorted, first wins) with a warning.
5. Template resolution
For the selected policy, the program's template_posting/6 facts describe the
posting specs (sequence, direction, role, amount/currency variables). The
account_role/3 rules resolve each symbolic role (:cash_account,
:client_liability_account, :fee_income_account, ...) to a concrete account
code — either from event-provided facts or a static account_role_static/2
fallback.
6–7. Journal & posting builder
Logistiki.Accounting.JournalBuilder and Logistiki.Accounting.PostingBuilder
materialize the knowledge result into a draft %Journal{} with draft
%Posting{} structs. Datalog never constructs structs; Elixir does.
8. Ledger invariant validation
Logistiki.Accounting.InvariantValidator enforces the hard accounting
invariants in pure Elixir (debits = credits per currency, positive amounts,
leaf posting accounts, idempotency uniqueness, exact reversal). These checks
never depend on Datalog.
9. Ledger backend execution
The configured backend (Logistiki.Ledger.Simulation or
Logistiki.Ledger.Beancount) executes the journal — persisting it as posted
and (for the Beancount backend) verifying it with Beancount.check/1.
10. Projection generation
Balances, statements, trial balances, and other projections are computed from
the now-immutable postings by Logistiki.Projections.ProjectionEngine.
11. Audit evidence
Every stage is recorded as an audit_events row by Logistiki.Audit, so the
full chain — event → rules → policy → template → journal → postings → ledger
result — can be explained later.
Telemetry
Each stage emits :telemetry events (see Logistiki.Telemetry.events/0), e.g.
[:logistiki, :knowledge, :evaluate, :stop], [:logistiki, :journal, :build, :stop],
[:logistiki, :ledger, :execute, :stop], [:logistiki, :audit, :write, :stop].