Rates Engine v0.5.0-rc.40
Pre-release[v0.5.0-rc.40] — 2026-05-11
Added
-
Ansible: monitoring playbook + log-discipline task codify the
post-incident operator lore from 2026-05-10. The
configs/ansible/roles/prometheus/templates/alertmanager.yml.j2
template now uses thepage/ticket/informational
severity vocabulary matching every rule indeploy/monitoring/
andconfigs/prometheus/rules.r1/, plus adeadmansswitch
receiver wired to a Healthchecks.io URL. New
configs/ansible/playbooks/monitoring.ymlinvokes the role on
archival_nodes. Newconfigs/ansible/roles/archival-node/tasks/ 15-log-discipline.ymlinstalls an/etc/logrotate.d/rsyslog
override (100 MB cap, 7-rotation history, gzip-compressed) and
/etc/systemd/journald.conf.d/00-cap.confwith
SystemMaxUse=500M+SystemKeepFree=300M— both directly
addressing the operational follow-ups in
internal/incidents/data/2026-05-10-redis-writes-blocked-disk-full.md.
A from-scratch r1 (or future R2/R3) rebuild now picks up the
fix automatically. Operator action: provision
/etc/default/alertmanager-secrets(Slack + Healthchecks URLs)
and runansible-playbook -i inventory/r1.yml playbooks/monitoring.yml
on r1 to actually start delivering alerts. -
/v1/assets/{slug}global view +/v1/coinsdeprecation
(R-018 Phase 1.4a). The/v1/assets/{asset_id}route now
dispatches on the path parameter: a verified-currency slug
(usdc,eurc,aqua, …) returns the newGlobalAssetView
wire shape — cross-chain identity + price block from the Phase
1.3a three-tier fallback chain + anetworks[]list with
Stellardeep_linkentries pointing at the per-Stellar-asset
view. Canonical asset_ids (USDC-GA5Z…,native,C…,
fiat:USD) still route to the existing per-Stellar-asset
surface unchanged. Production wiring binds
aggregate.GlobalPriceReaderto*timescale.Store+ the
existing Redis triangulated looker, with
external.AggregatorSources()as the tier-2 source list.
/v1/coinsand/v1/coins/{slug}now emitDeprecation: trueLink: </v1/assets/{slug}>; rel="successor-version"headers
per RFC 9745 / 8288 — runtime behaviour unchanged so the
explorer (Phase 1.5) keeps working. Actual/v1/coins
deletion (1.4b) lands after the explorer migrates.
-
Three-tier global-price fallback chain
(R-018 Phase 1.3a). Newinternal/aggregate/ComputeGlobalPrice
walksvwap_native→aggregator_avg→triangulatedin
order, returning the first tier whose data satisfies its
threshold (trade-count floor for tier 1, freshness window for
tier 2). Result carriesPrice,Authority(one of the three
tier labels),Sources, andAsOfso Phase 1.4's/v1/assets/ {slug}global view can surface provenance per response. New
external.AggregatorSources()helper returns the aggregator-
class source names in deterministic order — matches the
pre-existingFXSources()pattern. The cross-chain
ticker-bucketed VWAP CAGG (1.3b) is explicitly deferred — it's
algorithmically distinct from the per-pair VWAP and only
meaningful once we ingest non-Stellar-chain trades. -
Catalogue-driven CoinGecko coverage + aggregator-price reader
(R-018 Phase 1.2). The CoinGecko poller's ticker map and the
indexer's aggregator pair set now derive from the verified-
currency catalogue: adding a verified currency with a
coingecko_idininternal/currency/data/seed.yaml
automatically extends polling coverage. CG's hardcoded
ticker-to-slug map (13 entries) remains a fallback for tests
and pre-Phase-1.2 callers. New storage method
Store.LatestAggregatorPricesForPair(ctx, base, quote, sources)
returns the most-recent observation per aggregator-class source
— the seam Phase 1.3'saggregator_avgprice-authority tier
consumes. Reuses the existingoracle_updateshypertable (no
new migration). CG catalogue-augmentation worker (top-N
market-cap refresh) deferred — separate trust surface; the
hand-curated seed suffices for v1. -
Unverified-ticker-collision warning on
/v1/assets/{id}
(R-018 Phase 1.1). Requests for an asset whose code matches a
verified currency's Stellar ticker (USDC, EURC, AQUA, …) but
whose issuer doesn't match the verified entry now carry an
unverified_warningbody pointing at the verified canonical
asset, plusflags.unverified_ticker_collision = trueon the
envelope. Warning body fields:verified_slug,
verified_asset_id,verified_name,verified_issuer,note
(a one-sentence message rendered verbatim by clients). Powered
by a newinternal/currencypackage + a 26-currency seed
YAML embedded in the binary
(internal/currency/data/seed.yaml) covering Stellar native +
major USD stablecoins (USDC, USDT, PYUSD), EUR stablecoins
(EURC), Stellar-native tokens (AQUA, yXLM, SHX, VELO, BLND,
PHO, yUSDC) and globals without verified Stellar issuers (BTC,
ETH, SOL, BNB, XRP, ADA, DOGE, AVAX, MATIC, DOT, LINK, UNI,
AAVE, WBTC). Foundation for the multi-network assets migration
(docs/architecture/multi-network-assets-migration.md);
Phases 1.2 (CG/CMC connectors) → 1.5 (explorer migration) build
on this catalogue. OpenAPI +pkg/client.UnverifiedWarning+
Flags.UnverifiedTickerCollisionin lockstep. -
/v1/methodology— machine-readable summary of the active
aggregation policy (R-023). Returns the VWAP method,
per-endpoint outlier filters, the operator's stablecoin →
fiat-USD proxy allow-list, the four source classes
(exchange / aggregator / oracle / authority_sanity) and which
contributes to the served price, the flat list of registered
venues with class / weight / VWAP-inclusion flags, and pointers
to the long-form ADRs that govern each section. Designed for
transparency consumers (compliance, auditors, integrators) who
want to verify the policy without parsing the explorer's HTML
/methodology page or chasing ADR cross-refs. Sub-millisecond —
derived from compile-time constants + the in-memory source
registry + operator config; no DB call. OpenAPI spec, pkg/client
Methodologyshape, and explorer types kept in lock-step.
Three regression tests pin baseline shape, peg-config
round-trip, and empty-pegs deployment.
Changed
-
/v1/marketsdefault sort changed frompair(alphabetical)
tovolume_24h_usd_desc(R-014). The alphabetical default
surfaced spam tokens (0-…,0TAX-…,0x1F3D4-…) at the top
of every cold listing — useless for the "what's interesting on
Stellar" query and the explorer always passed
?order_by=volume_24h_usd_descexplicitly to work around it.
Now the implicit default matches what every consumer wants.
Callers paginating the entire universe of pairs in lex order can
still pass?order_by=pairexplicitly. Cursor-format
compatibility note: because the cursor is sort-key-tagged
(validated viaValidateMarketsCursor), cursors generated under
the old alphabetical default will return 400 against the new
default — pass?order_by=pairalongside the cursor to resume
the alphabetical pagination, or drop the cursor and start fresh
on the new default. -
/v1/observationsnow setsflags.triangulated=trueon an
empty result when /v1/price would have served a value via the
Redis VWAP cache or stablecoin-fiat proxy. The endpoint is
raw-per-source by ADR-0018, so a triangulated pair has no rows
to return — but the emptydata: []was indistinguishable from
"this pair is unpriced" and sent integrators chasing nonexistent
data. The hint never fires when the caller passed?source=
(source-filtered queries are asking about a specific venue, not
the aggregate). R-011 indocs/review-2026-05-10.md.
Fixed
/v1/assets/{id}and/v1/assets/{id}/metadatanow run their
responses through the sameenrichIssuerknown-issuers backfill
that/v1/issuersalready used. Pre-fix, the two surfaces
disagreed on whether SEP-1 metadata existed for the same issuer:
/v1/assets/USDC-G…reportedhome_domain: null, sep1_status: "not_applicable"while/v1/issuers/G…reported
home_domain: "centre.io". The asset surface relied on the
watched-set sep1-refresh worker having populated the storage row,
which doesn't run on a fresh deployment. Now both surfaces fall
back to the curatedinternal/api/v1/known_issuers.gomap when
the storage row is empty. R-016 indocs/review-2026-05-10.md.
Added
/v1/chartenvelope carries atruncatedflag plus
data_starts_atandrequested_fromtimestamps when the
requested timeframe extends before the deployment's earliest
available data. r1 today only retains ~7 days of high-resolution
history but still accepts?timeframe=1y— pre-fix, consumers
couldn't tell whether the returned 7 daily points were the last 7
days of a long history or all the history this deployment has.
timeframe=allalways reportstruncated=false(that timeframe
means "everything you have" by definition). OpenAPI + pkg/client
ChartSeries updated. R-013 indocs/review-2026-05-10.md.
Fixed
-
Handler-timeout paths return 503, not 500, when the per-call
context deadline fires and Postgres returns its own
canceling statementerror.errors.Is(err, context.DeadlineExceeded)doesn't match thepq.Error
SQLSTATE 57014 that lib/pq surfaces after the v3 cancel-request
flow — so/v1/markets,/v1/pools, and/v1/coinswere
500ing on the cold-cache path that the 8s ceiling was
specifically meant to convert into a retryable 503. New
handlerTimedOut(callCtx, err)helper consults the per-call
context'sErr()as the authoritative signal. R-021 in
docs/review-2026-05-10.md. -
All-time-high (
/v1/coins/{slug}.ath,?include=athon
/v1/coins) is now derived fromprices_1d.vwapinstead of
prices_1d.high_price. The single-tick max was being polluted
by sub-stroop dust trades — XLM was reporting an ATH of $1.03
on r1 because a single 1-stroop ↔ 1-stroop SDEX dust ManageOffer
cross setmax(quote/base) = 1.0for the day. Day-VWAP is
volume-weighted and naturally rejects dust. Same family of fix
as/v1/ohlcoutlier filtering. R-008 in
docs/review-2026-05-10.md.
Changed
/v1/ohlcapplies a 4σ outlier filter by default. OHLC's
High/Low have no statistical robustness — a single 1-stroop ↔
1-stroop SDEX dust ManageOffer cross at the offer-book boundary
was pinning XLM/USDhigh=$1.0000000000on the live r1 surface
even though the real cluster was at ~$0.168. The handler now
routes trades throughaggregate.FilterOutliers(already used by
the aggregator orchestrator and/v1/vwap) before
aggregate.ComputeOHLC. New?outlier_sigma=Nquery param lets
callers tune the threshold; passoutlier_sigma=0to opt out for
raw extremes (the explorer's "show every print" view). The
per-bar volume + trade_count fields reflect the post-filter set,
matching the High/Low semantics. R-007 in
docs/review-2026-05-10.md.
Fixed
/v1/price/batchno longer silently drops asset_ids whose price
comes via the stablecoin → fiat:USD proxy chain. The batch path
inlined only the Redis-VWAP and fiat-cross-rate fallbacks, so
asset_ids that returned 200 from the single-asset/v1/price
(because they hittryStablecoinFiatProxy) were missing from the
batch envelope without warning.fetchBatchRownow shares the
full three-layerpriceFallbackchain withhandlePrice.
Regression test added inprice_batch_test.go.
R-005 indocs/review-2026-05-10.md.