Domain Invariants & Audit Trail

v0.1.0

The 8 non-negotiable General Ledger invariants, domain exception catalog, and append-only forensic audit logging.

Domain Invariants & Forensic Audit Trail

The General Ledger is a statutory system of record. Every financial action must adhere to strict mathematical rules, and every change must be permanently verifiable.


The 8 Non-Negotiable Invariants (“The Law”)

flowchart TD
    LAW["The 8 Non-Negotiable Ledger Invariants"]
    
    L1["1. Zero-Sum Balance Parity<br/>Σ Debits = Σ Credits ($0 drift)"]
    L2["2. Ledger Immutability<br/>Row-level edits forbidden; Storno reversals only"]
    L3["3. Leaf-Only Postings<br/>Aggregator parents reject direct journal lines"]
    L4["4. Fiscal Period Hard Locks<br/>Closed periods reject all mutation attempts"]
    L5["5. Maker-Checker Segregation<br/>Voucher creator cannot approve own entry"]
    L6["6. Base Rate Immutability<br/>Base currency FX rate permanently locked to 1.0"]
    L7["7. Functional GL Denomination<br/>Postings persist in functional minor units"]
    L8["8. ACL Domain Isolation<br/>Operational domains never touch accounting tables"]

    LAW --> L1
    LAW --> L2
    LAW --> L3
    LAW --> L4
    LAW --> L5
    LAW --> L6
    LAW --> L7
    LAW --> L8

Exception Catalog & Diagnostic Reference

When a domain invariant is violated, the engine halts with a specific, strongly-typed domain exception:

Exception ClassTrigger ConditionCorrective Resolution
UnbalancedJournalEntryExceptionTotal debit minor units do not equal total credit minor units.Adjust line items so that total debits equal total credits to the exact minor unit ($0 imbalance).
ImmutableJournalEntryExceptionDirect SQL update() or delete() attempted on a POSTED or REVERSED voucher.Never edit posted vouchers. Issue a Storno reversal via ium()->accounting()->journalEntries()->reverse(...).
NonPostingAccountExceptionAttempted to assign a non-leaf aggregator account (is_leaf = false) to a journal line.Target a specific child leaf account (is_leaf = true).
FiscalPeriodClosedExceptionVoucher submission targeting a locked (CLOSING_REVIEW) or sealed (CLOSED) period.Use an open period, or perform an administrative review unlock if authorized.
MakerCheckerViolationExceptionThe user who created the draft attempts to approve or post it.Ensure the approver ID differs from the creator ID ($approverId !== $creatorId).
BaseCurrencyRateImmutableExceptionAttempted to set an exchange rate on the organization’s base currency.The base currency is the denominator (rate = 1.000000). Quote rates on non-base currencies only.

Append-Only Forensic Audit Trail

All lifecycle transitions, approvals, reversals, and configuration mutations automatically write an entry to ium_accounting_audit_logs.

What Is Recorded?

  • Action Type: DRAFT_CREATED, SUBMITTED, APPROVED, POSTED, REVERSED, PERIOD_CLOSED, FX_RATE_UPDATED.
  • Actor Metadata: User ID, IP address, user agent.
  • Payload Snapshot: Complete serialized JSON representation of the entity state before and after the action.
  • Reason String: Mandatory justification for reversals, overrides, or period state changes.

Querying the Audit Trail

// Retrieve audit trail for a specific voucher
$auditLogs = ium()->accounting()->audit()->forEntry($voucherId);

foreach ($auditLogs as $log) {
    echo "Action: {$log->action}\n";
    echo "By User: {$log->actor_id} at {$log->created_at}\n";
    echo "Reason: {$log->reason}\n";
    dump($log->snapshot);
}