Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Indexer

governance-event-indexer moves governance events from the public Stellar testnet RPC into Postgres — raw events are preserved and parser-backed event decodes are stored durably — and serves an initial read-only GraphQL API for governance-v1 schema.

Status: ingestion, durable event decode, the initial GraphQL surface, the deploy recipe (I6) and the clean-clone prove script (I7) are built. Proposal, vote and delegate projections and reconciliation (I5) remain planned. The GraphQL endpoint serves health and normalized event records; proposal, impact and delegate fields remain unavailable until their tables are implemented.

What is built

Registry and pins

  • The pinned deployments.json from the parser repo, hash-verified on every load; GOVERNOR_REGISTRY_OVERRIDE adds contract ids at runtime from validated strkeys, failing closed on anything malformed.
  • Three pins verified offline in every PR: parser.lock (WASM asset), schema.lock (SDL) and registry.lock (deployments). A nightly workflow checks that the pinned URLs still resolve to identical bytes.

Ingestion

  • Windows. getEvents in ranges of at most 1000 ledgers, because the public RPC times out on wider ranges; INGEST_WINDOW_LEDGERS is rejected above 1000.
  • Cursors. One persisted cursor per registered contract, initialized at the start of the RPC’s retention window.
  • Storage. Raw events land in raw_events, keyed (ledger, tx_hash, op_index, event_index), inserted idempotently. A window commits together with its cursor advance, so a killed process re-fetches and converges.
  • Retention gaps. A cursor behind the RPC’s oldest ledger writes the unreadable range to ingest_gaps with reason retention and advances past it in the same transaction — loud, never a silent skip.
  • Replay. node dist/cli.js replay <dir> runs committed raw responses through the same raw-plus-decode path, tagging rows source='fixture-replay'. Live rows are source='live', and a later write never flips a row’s source.

Storage

Five tables exist after migrate (forward-only, tested from an empty database):

TableHolds
raw_eventsevery ingested event, keyed (ledger, tx_hash, op_index, event_index), with source = live or fixture-replay
decoded_eventsparser-versioned normalized topics/payload and explicit VERIFIED or UNVERIFIED status for each raw event
ingest_cursorsone cursor per registered contract, advanced in the same transaction as its window
ingest_gapsunreadable ledger ranges with the reason (retention), written instead of skipping silently
indexer_metaindexer bookkeeping (schema version and run metadata)

Proposal-level tables (proposals, proposal_actions, risk_flags, votes, power_checkpoints, delegations, delegates, reconciliation_runs) arrive with I5 and later. Event-level decode is persisted in decoded_events.

Retry behaviour

Bounded exponential backoff with jitter on RPC failures. There is no unbounded retry loop: a failure that exceeds the bound surfaces as an error, and the cursor does not advance past data that was not stored.

What is not built

MilestoneState
I3 decode through the pinned Script3 adapterevent-level decode persisted through the pinned WASM; proposal projections remain planned
I4 GraphQL (Yoga) implementing the vendored SDL exactlyinitial health and event connection implemented; proposal/impact/delegate fields remain planned
I5 reconciliation of SQL tallies against on-chain readsnot built; drift checks remain planned
I6 Docker/compose deploy reciperecipe and deploy.yml shipped; no instance has been deployed
I7 clean-clone prove scriptbuilt; asserts GraphQL values against the committed fixture replay, never yet run against a live indexer

Decoding governor events in TypeScript would put governor logic outside the parser’s GovernorAdapter and break the project’s rule that governor logic lives in the parser. The event-level boundary now uses the pinned parser artifact; remaining work is the durable projection of proposal entities.

Checks

CommandWhat it proves
npm run check-pinsvendored bytes match all three locks, offline
npm run typecheck, npm run lintstrict TypeScript and ESLint pass
npm run test:unit59 tests, no database, no network
npm run test:integration18 tests: migrations from empty, windowing, idempotency, restart safety, gaps, replay

Provenance

The indexer consumes the parser through pins: the SDL and registry at parser commit ba3bf1a3…, the WASM asset v0.1.0-alpha.2. Its README pin table is the authoritative list; this page summarises it, and the revisions this summary was written against are recorded in docs-site/sources.lock.