Data and ledger model
The four stores, the order they are written in, what each record holds and which view hides which field.
GAVEL keeps its truth in a few append-only or compare-and-set files. This page names every record, says who writes it and when, and shows what is hidden from whom.
Data and ledger model: what is written, in what order, and who sees what
Scroll the figure sideways, or open it full size.
Step 1. A command becomes events. The room log holds the event first (appendEvents with the expected seq, so a gap or a repeat is a conflict).
packages/server/src/boot/compose.ts (stores), packages/engine/src/ledger/chain.ts, ledger/public-export.ts, projections.ts, packages/payments/src/types.ts and saga/trail.ts, and the redaction in packages/server/src/auth/authorize.ts and http/export.ts. The auditor projection is a library function today; no server route calls it. Write order comes from the note in store/file-ledger-store.ts (decisions D-0012 and D-0014).Text version of this diagram
Four stores and four views.
- Room log: RoomEvent records with gapless seq, plus snapshots and the token denylist. Written first.
- Engine ledger: hash-chained entries, one file per auction. Written before the event is acknowledged.
- Payment journal: one per auction, saved by compare-and-set, only after the hammer is in the ledger.
- Holds, webhook ids, audit rows and the kill switch file.
- Operator (admin key) sees ceilings, over-ceiling rejection amounts, hold references, names and transcripts.
- The auctioneer export drops the ceiling, the over-ceiling amount, names, hold references and transcripts.
- The public replay bundle (export version 4) drops ceilings, hold references, rejection amounts, leader fields and transcripts; names never enter the ledger.
- The auditor projection shows the whole ledger, except that bid amounts and recording details of a sealed lot are withheld until it is hammered.
Beginner view: why more than one log
A live auction needs two different kinds of memory.
- What happened in the room (who joined, which bids came in, what the screen showed). That is the room log: ordered events, numbered 1, 2, 3 with no gaps, so a reconnecting screen can ask for "everything after 41".
- What the auction rules decided (who leads, who won). That is the engine ledger: each entry carries the hash of the entry before it, so changing an old entry breaks every hash after it. Anyone holding the chain can check it.
Money gets its own record, the payment journal, because settlement has to survive a crash halfway through a PayPal call. The journal says what GAVEL intends to do before it does it.
The write order
The room log holds the event first.
The engine ledger is written before the event is acknowledged.
The payment journal is saved only after LOT_HAMMERED is in the ledger.
Every saga transition is one compare-and-set save of the journal; its audit row is appended after the save and carries the journal history seq, so a crash between the two is repaired from the journal on the next run.
The first three lines are the note in packages/server/src/store/file-ledger-store.ts (decisions D-0012 and D-0014). The last is the contract in packages/payments/src/saga/trail.ts.
Records
RoomEvent is a discriminated union in packages/shared/src/events.ts. The store port (RoomStore) appends events from expectedSeq + 1 with no gap and throws conflict when the stored last seq differs.
Types: auction_created, bidder_registered, bid_accepted, bid_rejected, confirm_requested, confirm_resolved, voice_dropped, control, state_changed, hammered, ceiling_revoked, settle_requested, settlement_update.
The same store keeps snapshots, the single-use token ids (jti) and the denylist. In Postgres these are the tables events, snapshots, jti_used, jti_denied and journals.
Who sees which field
Exports are not public
GET /admin/export returns the private ledger with ceilings, hold references and transcripts. It needs the admin key and must never reach a public place. See Backup.
| Field | Operator (admin key) | Auctioneer export | Public replay bundle | Auditor projection |
|---|---|---|---|---|
| Bidder ceiling | shown | withheld | withheld | shown |
| Over-ceiling rejection amount | shown | withheld | withheld | shown |
| Hold reference | shown | withheld | withheld | shown |
| Bidder display name | shown | withheld | not in the ledger | not in the ledger |
| Voice transcript | shown | withheld | withheld | shown |
- Auctioneer export (
GET /api/auctions/:id/export, roles auctioneer and admin):redactEventForExportdrops the ceiling and the amount of ceiling rejections;redactPersonaldrops names, hold references, the auctioneer subject and transcripts for everyone but the admin key. - Public replay bundle (
GET /admin/replay-bundle, built from the verified ledger): export version 4. Ceilings, hold references, leader fields, transcripts and the amount of an over-ceiling rejection are removed rather than hashed, because a ceiling is a small number and a hash of it can be guessed. After the first removed field, entries carryfullHash: null(or a keyed commitment when an auditor passes a key), so a viewer cannot test amounts by hashing forward. Anyone can verify the public chain withverifyPublicExport. - Auditor projection (
projectAuditor) is a library function; no server route calls it yet.
Sealed lots
In the second_price mode bids are sealed until the hammer. projectPublic shows a null price and leader; projectAuditor withholds the amount, transcript, parsed amount, utterance id and audio offsets until the lot is hammered or unsold; the public export drops sealed amounts until the hammer. Proxy lots drop each bid amount (the bidder's secret maximum) for good.
Where this lives in the code
- packages/engine/src/events.tsledger event types and LedgerEntry
- packages/engine/src/ledger/chain.tssealBatch, hashEntry, verifyChain
- packages/engine/src/ledger/public-export.tsthe redacted public chain (version 4)
- packages/engine/src/projections.tsprojectPublic, projectBidder, projectAuditor
- packages/shared/src/events.tsRoomEvent
- packages/shared/src/ports.tsRoomStore
- packages/server/src/store/file-ledger-store.tsone file per auction, write order
- packages/server/src/store/file-room-store.tsthe room log file
- packages/server/migrations/0001_init.sqlPostgres tables
- packages/payments/src/types.tsJournal, HoldRecord, PaymentAudit
- packages/payments/src/saga/trail.tsaudit rows recovered from the journal
- packages/server/src/http/export.tsauctioneer export and redactPersonal
- packages/server/src/admin/replay-bundle.tspublic replay bundle
Last updated on