Architecture

The export boundary

Axorum is a control plane, not a system of record for the enterprise. What leaves it is a frozen interchange contract: one committed transaction, projected into an accounting-and-audit record that an ERP, a warehouse, or an auditor can consume without knowing anything about deontic logic.


The contract

The unit of export is one committed transaction, rendered as a canonical record: its conservation legs, its normative verdict, its provenance, the obligation lifecycle events it caused, and the identity of the actor that proposed it.

The contract is versioned and frozen — axorum.export.v0. It is deliberately decoupled from the internal deontic and substrate types. Those are free to evolve; the record on the boundary is not, because everything downstream is built against it. A change to the wire is a deliberate, ratified act, not a consequence of a refactor.

This is the same discipline the agent plane's OpenAPI document is under, applied to the other side of the system.

Change data capture

Records leave through a read-only, resumable, cursored stream over the committed normative log: transactional-outbox semantics, with faithfulness chosen over availability at every fork in the design.

A consumer holds a cursor, reads forward, and can stop and resume without loss or duplication. It cannot affect the ledger, cannot hold it up, and cannot see anything that has not committed. If the choice is between a stream that keeps flowing and a stream that is exactly right, this one is exactly right.

Encodings and tiering

The same canonical records project into two encodings.

JSON is the interchange encoding: what an adapter, a webhook, or a person reads.

Arrow and Parquet are the analytical encoding: the same records as columnar batches, with 128-bit minor units carried exactly as Decimal128(38, 0). Exactly, not approximately — a warehouse that rounds a ledger is not a warehouse, it is a rumour. The enum vocabulary is test-pinned against the JSON projection, so the two encodings cannot drift.

Frozen history tiers into byte-stable Parquet segments under an append-only, versioned manifest. Duty fate changes freeze alongside as a companion event stream, driven by a watchlist of open duties — so the archive can answer what became of a duty without replaying the whole log.

The freezer is proven by invariants that compare it against the ledger rather than against itself: leg sums are zero, the record count matches the CDC stream, every duty's fate in the lake equals the answer the reporting subsystem gives, and re-freezing the same input produces byte-identical output.

Query

A read-only SQL daemon sits over the frozen history. It mounts the manifest as its catalog, verifies content hashes when it opens them, and fails closed if they do not match.

It exposes the canonical views — transactions, legs, obligations joined to their fates, refusals — and answers SQL with exact decimal arithmetic, including the obligations trial balance as of any tick. It is SELECT-only, bearer-token gated, and runs as a separate daemon from the write path, deliberately: the process that answers questions about the ledger has no ability to change it.

Reconciliation

The wedge into an existing accounting stack is reconciliation: a pure match of committed Axorum records against the ERP's posted state, classified into a small number of outcomes and narrated in compliance English.

Matched. Amount break. Posted but not authorized — something is on the ERP's books that Axorum never permitted. Authorized but not posted — something Axorum permitted never reached the books. Plus timing breaks, which are the difference between the two systems' clocks and not a fault in either.

The second and third classes are the interesting ones, and they are the reason an organization would run this alongside a system it already trusts. They are how a control plane earns its place: by naming, precisely, what the system of record cannot account for.

Previous
The substrate and the plane
Next
SDKs