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.