Skip to main content
Concepts
DraftLast reviewed 2026-06-23

Event store

trms-event-store is the implementation behind event sourcing: an append-only table in PostgreSQL holding every event ever recorded, plus the projectors that fold those events into queryable state.

What an event looks like​

Every event carries an aggregate type and id, an event type, a monotonic version number, a JSON payload (domain details plus any schema extensions), and metadata: the acting user, their roles, the scopes resolved for the request, the client type (api, mcp_agent, cli, or batch), a correlation id, the endpoint invoked, an idempotency key, and — where relevant — the instruction that originated the deal and the approval context that authorized it. Two timestamps are tracked: valid_time (when the event is true of the business) and recorded_at (when it was written), supporting bitemporal queries without bolting reconstruction logic onto the read side.

Append-only by construction​

UPDATE and DELETE are revoked at the database level for the events table. This isn't an application-layer convention that a careless migration could violate — it's enforced by PostgreSQL grants, so even a compromised application credential cannot rewrite history. The only way to "undo" something is to append a compensating event (deal_rejected, journal_reversed, payment_cancelled), which is itself permanent.

Projectors​

Each aggregate has a corresponding projector — DealProjector, ValuationProjector, SettlementProjector, PaymentProjector, JournalProjector, InstructionProjector, CloseoutProjector, NettingProjector — that consumes new events and updates a denormalized table shaped for fast reads (e.g. current deal status, with instruction linkage; journal entries, with valuation and reversal linkage). Projection updates happen synchronously, in the same transaction as the event append, so there's no read-after-write lag to reason about.

Replay​

Because projections are pure functions of the event log, they're disposable. Rebuilding a projection table from scratch and replaying every event for an aggregate produces the same result as the original incremental updates — this is what makes schema evolution and bug-fixed projector logic safe to roll out without a one-off backfill script per release.

Idempotency​

Writes carry an Idempotency-Key header honored for 24 hours, so retried requests (a flaky network, an agent retrying a tool call) append at most one event rather than double-booking a trade. Optimistic concurrency on (aggregate_id, version) catches the case where two writers raced against the same aggregate; the loser gets a 409 and re-reads before retrying.

Two PostgreSQL-specific tables back the store (see Schema catalog and the ERD): the events table itself, partitioned yearly, and a sequence/index structure that keeps per-aggregate reads fast even as the log grows. For the tamper-evidence layer on top of this storage, see Hash chain.