Core concepts
Obligations
An obligation is a commitment a transaction created and a later transaction must satisfy. Commitments are first-class, queryable state — the trial balance for authority, kept alongside the trial balance for money.
Why commitments are separate objects
A purchase that requires a receipt is not one fact. It is two: the purchase, which happened, and the receipt, which is owed. They are separated in time, they are performed by potentially different actors, and the second one can fail to happen at all.
A system that records only the first fact cannot tell you what is outstanding. Axorum records the second as a real object with a lifecycle, a due date, and an identity — so that at any moment you can ask what is outstanding, who owes it, and when it falls due, and get an exact answer from the ledger rather than an estimate from a report.
The lifecycle
Every obligation instance moves through a finite state machine.
| State | Meaning |
|---|---|
Dormant | Instantiated, but its guard has not yet become true. |
Active | Live and owed. The deadline is anchored and running. |
Discharged | The discharging action committed. Terminal. |
Violated | The deadline passed unfulfilled, or a prohibited act committed. Terminal. |
Waived | An authorized waiver released it. Terminal. |
Expired | Its applicability window closed before it ever triggered. Terminal. |
The four terminal states are terminal. There is no path back out of Violated for the instance itself — a breach that could be undone would not be a breach.
The transition function is pure and total. Any state-event pair that is not a legal transition is a typed error, never a panic and never a silent no-op.
The open set — every instance in Dormant or Active — is the queryable working set, served by GET /api/v1/obligations. It is what an agent reads to learn what it currently owes, and what the deadline sweep walks to find what has fallen due.
Due dates and the clock
Time enters the ledger as an explicit, monotone tick, never as a clock read. A heartbeat advances it, and each advance sweeps the open set for duties whose deadlines have passed. Those breach, in the log, at a known tick.
Deadlines anchor in one of two ways. A duty declared within(d) anchors at the tick it triggered and breaches when the tick exceeds trigger + d. A duty declared by(t) breaches when the tick exceeds t, regardless of trigger tick.
That second form admits a strange and important case: a duty can be born already breached, when the resolved deadline is strictly before its anchor tick at instantiation. Axorum does not swallow it. The instance is created directly in Violated, its reparation is instantiated at the same tick, and the audit trail carries the born-violation as a fact. Expire-at-birth would silently swallow a norm, and validation-time rejection is impossible because the anchor is a runtime tick.
When a commitment is not met
The interesting question about a duty is not what happens when it is met. It is what happens when it is not.
A rule may declare a contrary-to-duty reparation: what is owed because the primary duty was breached. On breach, the reparation is instantiated automatically, at the violation tick, bound to the same actor. The missed receipt opens a duty to file an exception report. The late payment opens the penalty.
This is contrary-to-duty reasoning, and it is what separates a deontic model from an authorization check. The organization's real policy is not "receipts must be filed." It is "receipts must be filed, and when one is not, here is what must then happen." Both halves are policy. Both halves are enforced.
Reparation is single-level: it does not cascade a further reparation. A deliberate bound, not an oversight — unbounded chains of repair duties are how a control system becomes unanalyzable.
Identity and idempotence
An instance's id is derived by UUIDv5 over a frozen namespace from its provenance-determining facts: origin, policy, source rule, actor, and anchor tick. Two replicas evaluating the same commit derive bit-identical ids. Nothing is minted from a clock or a random source — time-seeded id minting inside evaluation is forbidden.
A consequence worth relying on: re-incurring a duty whose derived id is already open is a no-op. The evaluator skips creation rather than minting a duplicate. An agent that retries does not owe the receipt twice.
The origin discriminator is part of the identity and separates a triggered duty from a reparation of a specific violated instance, so the two cannot collide even when rule, actor, and tick coincide.