Rates Engine v0.5.0-rc.56
Pre-release
Pre-release
·
718 commits
to main
since this release
[v0.5.0-rc.56] — 2026-05-19
Fixed
/v1/diagnostics/ingestionentriesis no longer silently 0 for
every source. ThefxHistoryReaderadapter
(cmd/ratesengine-api) wraps*timescale.Storeand forwards the
coverage-reader methods (FXCoverageStats,CAGGCoverageStats)
but was missing aSourceEntryCountsdelegate. So the
request-time type assertions.fxHistory.(SourceEntryCountReader)
infillIngestionEntryCountsfailed closed and returned silently,
leavingentryCountsnil →entries: 0for every source
(sdex included) even thoughsource_entry_counts(migration 0035,
maintained live by the indexer and reconciled by
seed-entry-counts) was fully populated (sdex 2.7 B, …). Shipped
missing in rc.55, so the status page showed all protocols at 0
entries. Added the one-line delegate (same precedent as the two
sibling forwards, which document this exact "renders empty"
failure mode) and made the!okpath Warn-log instead of failing
invisibly so this wiring-regression class can't recur unnoticed.extendWithLiveTailnow bridges interior sub-tip coverage gaps,
fixing the ~96% (Soroban) / 99.5% (SDEX) density cap. The
live-ingest tail was credited only above the top of the merged
backfill union. When a disjoint high gap-backfill island (e.g. the
62,606,296–62,613,951 gap re-fill) fragmented the union, a
~309,700-ledger interior span [62,296,595→62,606,296] — fully
populated by gap-free live ingest (22.8 M trades verified on r1) —
got zero credit, capping density at ~96% Soroban / 99.5% SDEX
(the same absolute hole over different genesis denominators).
The tail now also fills any gap between two merged backfill
intervals whose upper neighbour starts at/below the live cursor:
bracketed by backfill coverage on both sides and wholly within the
gap-free live span (ADR-0017 archivecompleteness), so live ingest
provably walked it. The honest guards are retained — the
[genesis, firstBackfillStart]lower boundary is never an
adjacent-pair interior gap so it stays uncovered (a
never-backfilled-low source still reads honestly, e.g. band's
pre-deploy history under the new #10 genesis), a never-backfilled
source stays 0%, and nothing is credited above the live cursor.sourceGenesisLedgernow holds exact first-WASM-deploy ledgers,
not rounded deploy-era constants. The per-source genesis is the
denominator ofbackfill_coverage[].density_pct, so a rounded
value was a two-way correctness bug under the "every source to
100%" invariant: rounded before the real deploy padded the
denominator with pre-existence ledgers (100% mathematically
unreachable), rounded after it silently hid genuine
early-history gaps (e.g.bandconst53_500_000vs real first
deploy50_842_736— ~2.66M ledgers of history structurally
invisible to the metric;reflector-fxconst51_000_000vs
real56_733_481— ~5.7M phantom pre-existence ledgers). All
on-chain sources now use the MINcreate_contractledger across
every routed contract (factory + instances, upgrade-in-place
aware), sourced from the per-source WASM-audit walk evidence
(docs/operations/wasm-audits/,r1-walk-2026-05-01); the doc
contract flips from "approx slack is fine" to "exact, zero
slack".defindexstays explicitly provisional pending its
per-WASM walk (BackfillSafe=false; audit in_progress).- Migration ownership invariant documented (
migrations/README.md
Rule 7).source_entry_counts(migration 0035) was applied
manually as thepostgressuperuser on r1, leaving it
superuser-owned; the rc.55 indexer's always-on entry tally and
ratesengine-ops seed-entry-countsthen hitpermission denied for table source_entry_counts (42501). Root cause is operational,
not schema:ratesengine-migrateruns as theratesengineapp
role (RATESENGINE_POSTGRES_DSN), so on correctly-applied deploys
(R2/R3/fresh) the table is app-owned by construction and needs no
GRANT — only r1's manual-as-superuser application was the anomaly.
Hot-fixed in place withALTER TABLE source_entry_counts OWNER TO ratesengine(canonical shape, matchestrades); a follow-up
GRANT migration was deliberately not added (it would error when
run as the app role against a superuser-owned object and is a
no-op otherwise — the fix is "apply as the app role", now a
documented Rule).