Stellar Index v0.6.0
[v0.6.0] — 2026-07-01
Minor bump for the breaking LC-001 assets split (/v1/assets is now
Stellar-only; see Changed). This section also captures the changes accumulated
since v0.5.0-rc.128 — the CHANGELOG promotion had lagged behind the release
tags (which reached rc.151), so the entries below span rc.129–rc.151 plus
the 2026-07-01 session.
Added
GET /v1/external/assets+GET /v1/external/assets/{slug}. The non-Stellar
side of the assets split (LC-001) — fiat currencies + reference-only coins. See
the BREAKING note under Changed.flags.divergence_checkedon the price envelope (CS-087). Signals whether
the cross-reference divergence check actually ran (≥1 responding reference). When
false,divergence_warningis blind and must not be read as "prices agree".- Regression guardrails. (1)
scripts/ci/lint-i128.sh(ADR-0003) — rejects
int64(x.Lo)i128 truncation + BIGINT/float monetary migration columns; (2) the
exhaustivelinter (ADR-0010), scoped to our domain enums, so a new
AssetType/Class/etc. variant added to a switch without handling fails CI;
(3) foundation-purity import rules (internal/canonical,nettools,
sources/external/scale,versionpinned to their dependency floor); (4) the
testcontainers integration suite now RUNS in CI as a blocking gate (CS-070 —
it was previously only compiled). All inmake verify/ CI. - Contributor docs for agents.
/CAPABILITY-INVENTORY.md(intent→symbol index,
to stop rebuilding existing helpers) +docs/contributing/checklists (add a
source / CEX / endpoint / metric / migration / observer). - Stablecoin self-peg pricing for the crypto-ticker form.
/v1/price?asset=crypto:USDC"e=fiat:USD(and the EUR/MXN pegs,
/v1/price/tip,/v1/observations,/v1/oracle) now returns the~$1peg
(price_type: "peg") instead of 404. The classic-issued form
(USDC-GA5Z…) already resolved via the operator's
usd_pegged_classic_assetslist (F-1232), but the abstract global-ticker
form the catalogue + explorer use (crypto:USDC,crypto:EURC, …) fell
through — no on-chain trade quotescrypto:USDCinfiat:USD.
tryStablecoinFiatProxynow consultsaggregate.FiatProxyfirst: when the
asset is acrypto:<STABLE>ticker whose peg fiat equals the requested
quote, it synthesises1.0(consistent with the aggregator's
stablecoins-as-fiat policy; a depeg still surfaces via the divergence
subsystem). A cross-peg quote (crypto:USDC/fiat:EUR) deliberately does NOT
fire — that's a real FX cross-rate. (launch-todo P2-4(b).) - SEP-41 token supply now served on
/v1/assets/{id}. A Soroban (SEP-41)
token has no LCM observer supply snapshot unless its contract is on an
operator watch-list — impractical at 10k+ tokens, so Algorithm 3 produced
nothing andtotal_supplywas null for every SEP-41 token./v1/assets/{id}
now falls back to the lake-derived per-token supply (ch-supply's
token_supply, Σmint−Σburn−Σclawback over the certified ClickHouse lake — the
same source/v1/assets/{id}/supplyalready uses) for Soroban-contract assets
with no observer snapshot, with a newsep41_lake_flowssupply basis. Every
SEP-41 token'stotal_supply/circulating_supply/market_cap_usdis now
complete + served from the full archive. The CH read fires only for Soroban
tokens (classic assets keep their Algorithm-2 snapshot). - Data-freshness watchdog — the "never get behind" alert. New
data-freshness.sh(every 15 min viadata-freshness.timer) emits per-domain
ingest-freshness gauges (stellarindex_data_freshness_{age_seconds,stale}) AND
the per-source ADR-0033 completeness verdict
(stellarindex_completeness_incomplete) to the node_exporter textfile
collector, with three alerts (stellarindex_data_source_stale,
stellarindex_completeness_incomplete,
stellarindex_data_freshness_watchdog_silent) + runbooks. Closes the gap the
audit found: the ingest gap-detector covered on-chain source gaps, but
reference oracles, FX, supply, the issuer-metadata cron, and the verdict itself
had no freshness alert — so coingecko rotted 11 days and sep1 metadata never
populated, unnoticed. Now any source past its cadence, or a real served≠lake
gap, pages. (launch-todo steady-state / "never behind".) massiveFX feed registered as an external source. The active fiat-FX
feed (internal/sources/forexworker, massive.com = Polygon's backend,
fx_quotespath) was missing fromexternal.Registry, so it never appeared
in/v1/sourcesandLookup("massive")fail-closed. Now registered as an
external FX source (ClassExchange/SubclassFX, off-chain). Fixes a latent
classification bug:IsOnChain("massive")previously fell through totrue
(which would have placed the off-chain FX feed on the explorer's Stellar
/networksurface); it is now correctlyfalse. (launch-todo P0-7.)- Two missing cron timers —
sep1-refresh+compute-completeness. Neither
had ever existed as a systemd timer, so both data sets silently froze: issuer
org_name/org_verifiedonly updated on a manualsep1-refresh, and the
ADR-0033 completeness verdict (completeness_snapshots) had drifted 17–21
days stale (watermarks at ~63.0M while the network was at 63.27M). New
Ansible templates under the archival-node role install both (daily, 05:12 /
05:30 UTC). The completeness timer runsrun-compute-completeness.sh— a
self-chunking per-source driver: it walks each source's
[watermark, tip]in 25k-ledger windows because (a) the watermark write
overwrites rather thanmax()s, so a global run would regress sources already
ahead, and (b) the high-volume SDEX projection reconcile blows ClickHouse's
12 GiB per-query limit above ~30k ledgers. Self-healing: any backlog (initial
catch-up or post-outage) is chunked automatically. Phase-0 of the launch
to-do (docs/operations/launch-todo.md). - Bidirectional SEP-1 org verification (
org_verified)./v1/issuers
now carriesorg_verified— true only when the issuer'shome_domain
stellar.tomllists THIS issuer's account back in its[[CURRENCIES]]
(i.e. the domain owner attests to the account). A one-directional
home_domain → ORG_NAMEmatch is spoofable: anyone can point their
account'shome_domainatcircle.comand inherit "Circle". The
explorer's issuer table renders a✓ Verifiedbadge only on the
bidirectional match, so org grouping/merging is trustworthy. The
sep1-refreshcron computes the flag (tomlListsIssuer) and persists it
in thesep1_payloadJSONB;/v1/issuersreads
sep1_payload->>'OrgVerified'. EachOKline now printsverified=….
Newsep1-refresh -issuer <g_strkey>force-refreshes one specific account
on demand (bypassing the staleness queue) — for onboarding a newly-verified
org without waiting for it to surface through ~43k pubnet issuers. stellarindex-ops state-snapshot— reads a history-archive checkpoint's full
current ledger-entry state (the bucket list) via the SDK's
CheckpointChangeReaderand tallies it by entry type. The read-only
foundation of the data-truth backfill (DATA-TRUTH-PLAN G1–G3): the served
ledger_entries_currentprojection only holds entries changed since ledger
~62M, so dormant-pre-62M accounts / trustlines / contract code+instances are
missing (the contract-WASM user-contract tail, incomplete account state +
issuer flags, possible trustline-supply undercount). A checkpoint snapshot is
the source of truth for that tail, read in one pass (no genesis replay). Its
-writemode backfills the contract_code + contract_instance entries (the
bounded G1 scope) intoledger_entry_changesvia a direct insert that writes
NO commit-marker ledgers row (so it never advances the completeness
watermark) — closing the contract-WASM gap for user contracts whose code was
deployed before the entry-capture window. Default mode is read-only tally.- Staff customer look-up (
/account/admin, audit 2026-06-19 item 16):
the cockpit's first tool is now live instead of a placeholder. New
staff-gatedGET /v1/account/admin/lookup?email=|slug=resolves an
account (tier, status, overrides) plus the users on it; the explorer's
admin page searches by email or account slug. Double-gated — RequireSession- an explicit
is_staffcheck (a non-staff customer gets 403, never another
customer's data) — and the access is audit-logged. Tier overrides + incident
tooling remain honestly marked "Coming in Phase 1.5" (they need write/
impersonation endpoints).
- an explicit
source_volume_1hcontinuous aggregate (migration 0068) — per-source
hourly trade-count + pre-aggregated USD-volume inputs. The source
page's activity chart now reads this CAGG instead of scanning raw
trades, making the 7d window cheap (the live derivation was ~18s for
the heaviest source, past the 8s API ceiling). The explorer's 24h/7d
toggle on/dexes/{source}+/exchanges/{source}is now live, and
the 24h sparkline is faster too./v1/sources?include=sparkline7dis
now surfaced by the frontend.on_chainboolean on/v1/sources(andexternal.IsOnChain) — true
for sources that observe the Stellar network directly (DEX, on-chain
oracles, lending, routers, bridges), false for off-chain reference
feeds (CEX / FX / aggregators / Chainlink).
Changed
- BREAKING — assets split into Stellar (
/v1/assets) and external
(/v1/external/assets) (LC-001)./v1/assetsnow lists Stellar assets
only (native XLM, classic credits, Soroban tokens, and verified-catalogue
currencies with a Stellar issuance — USDC, EURC, AQUA). Fiat currencies and
reference-only coins (BTC, ETH, …) — which have no Stellar issuance — moved to
the newGET /v1/external/assetslisting andGET /v1/external/assets/{slug}
detail. A non-Stellar slug now returns 404 on/v1/assets/{slug}(with a
cross-pointer; no redirect), and vice-versa — each asset resolves on exactly
one path.asset_class=fiatreturns an empty page on/v1/assets. The
explorer gains an/external/assetsdirectory + detail page and drops the
fiat chip from/assets. Root cause of the old mixing: the browse listing fed
offcatalogue.Browseable(), which drops reference-only coins but still
included fiat. - XLM circulating-supply basis is now honest when no SDF reserves are
configured. Previously stampedxlm_sdf_reserve_exclusioneven with an empty
reserve set (circulating == total), silently overstating circulating supply +
market cap; now emitsxlm_total_onlyso the misconfiguration is self-evident.
(The correct circulating still needssdf_reserve_accountsset in inventory.) - Dependencies brought to latest.
go-stellar-sdkv0.5→v0.6 (adapts the new
datastore.GetFilesize return; VERSIONS.md compat pass); the explorer + status
apps to React 19.2 / Next 16 / TypeScript 6 / Tailwind CSS 4 / ESLint 10 (flat
config; ESLint 10 via a one-lineeslint-plugin-reactpnpm patch), and the
React Compiler (babel-plugin-react-compiler1.0) is now enabled. - Re-enabled the
min_usd_volumeVWAP gate at $10k (r1 template). Pinned to 0
during the on-chain-only bootstrap; the CEX connectors now flow live volume
(binance/coinbase/kraken/bitstamp), so fiat:USD pairs clear the floor easily
while thin/manipulable pairs are gated. The CS-040 fix (per-sourceDecimalsin
the USD-volume sum) makes the gate FX-safe. - Prometheus TSDB relocated off the 49G OS root onto a ZFS dataset. The
~13G TSDB kept the root chronically >90% full (stellarindex_node_root_disk_full
alert). Moved todata/prometheus(zstd, ~12× → 1.31G on disk); root dropped
94%→60%. Added the dataset to the archival-node ZFS-role defaults so a rebuild
doesn't reland it on root. (launch-todo P0-5.) - The
/networkpage is now Stellar-only: "Top markets" reads
/v1/pools(on-chain DEX pools, not the CEX-dominated/v1/markets),
"Most active sources" + the venue-composition donut + the hero
Markets/Sources tiles all filter to on-chain sources. Off-chain
reference feeds stay on/exchanges+/aggregators. - The
/sourcesdirectory is now the Stellar on-chain source registry
(DEX / oracle / lending / router / bridge) — previously it listed
every venue and silently dropped the lending/router/bridge classes
(blend, cctp, rozo, defindex, soroswap-router now appear). - Source activity chart defaults to the 7d window when available.
- Market/asset OHLC chart now picks the finest granularity each window
allows under the API's 1000-bar cap (24h→5m, 7d→15m, 30d→1h, 90d→4h)
— far more detail per window.
Fixed
- Security & correctness audit remediation (2026-07-01). Highlights:
- SSE crash + DoS.
streaming.Hub.Publishcould send on a closed
subscriber channel (process-crashing panic) when a client disconnected
mid-publish — now guarded by a per-subscription mutex. The SSE handler
cleared its write deadline entirely, so a non-reading client leaked its
goroutine/conn/FD forever — now a rolling per-write deadline + a concurrent-
stream cap (CS-012 / CS-013). - Dashboard CSRF. The session cookie was
SameSite=Nonethough the
dashboard and API are same-site — nowSameSite=Lax, blocking cross-site
credentialed POSTs to the/v1/dashboard/*mutation handlers (CS-124). - SSRF. The OG-image edge function double-decoded + interpolated the URL
path unescaped into satori markup (blind SSRF) — now escaped/single-decoded
(CS-009). The three copies of the outbound-URL SSRF blocklist (SEP-1 +
webhook registration/delivery) diverged — two missed Oracle Cloud's metadata
IP192.0.0.192; unified into oneinternal/nettoolsguard (CS-008). - Issuer impersonation.
/v1/issuers/{id}droppedorg_verified, so the
explorer rendered an unverified self-declaredorg_nameas authoritative —
now surfaced + shown with a Verified/Unverified chip (CS-100). - Webhook replay. Delivery HMAC signed only the body — now timestamp-bound
(X-StellarIndex-Timestamp) so a captured delivery can't be replayed (CS-055). - Data-truth signals. Completeness watermark could regress to a stale tip
(now GREATEST-guarded, CS-083); a total divergence-reference outage counted
as success (now a distinctno_referenceoutcome + alert, CS-088); the
ingest cursor gauge advanced even on a failed persist (CS-029); dormant-pair
VWAP servedstale=falseforever (CS-017); the USD-volume gate assumed 1e8
for FX sources that stamp 1e6 (CS-040); negative circulating supply clamp
(CS-038). - Accessibility. The API-request dialog + mobile nav drawer gained a real
focus-trap/escape/restore; form errors/success now announce to screen
readers (LC-050 / LC-051 / LC-052). - Ops config. Alertmanager rendered webhook secrets world-readable (now
0640, CS-121); the sshd password-auth Ansible gate inverted on a string
override (now| bool, CS-120); the User-Agent was injected unescaped into
the plaintext magic-link email (CS-071).
- SSE crash + DoS.
- Completeness verdict false-negative on factory-gated sources (blend).
compute-completeness(the daily verdict, ADR-0033) never seeded the
factory-child gate registry — onlyverify-reconciliationdid. So its
childgates were the staticprotocol_contractsseed and went stale as new
pools deployed:blendreportedcomplete=false(expected=0) on windows
whose activity was on pools missing from the seed, while the live decoder
(self-seeding from deploy events) captured them — i.e. a checker bug, not a
served-data gap. Nowcompute-completenesspreseeds factory children from the
creation events[genesis, lo)before each re-derive (matching
verify-reconciliation), making the watchdog self-maintaining as pools deploy. - CoinGecko Pro key would have 404'd — the poller now auto-switches to
pro-api.coingecko.com. A Pro key (COINGECKO_API_KEY) only authenticates
against the paid host; the poller hard-coded the public host
(api.coingecko.com), so an operator upgrading to the paid tier (to fix the
dead oracle feed — it had hit the 10k free-tier limit) would have silently
kept failing. The poller now selectspro-api.coingecko.comwhenever a Pro
key is set and the endpoint wasn't explicitly overridden. (launch-todo P0-3.) sep1-refreshcould never reach good issuers — failed fetches now bump
sep1_resolved_at. A resolve failure (deadhome_domain, TLS error,
SSRF-blocked) used tocontinuewithout writing anything, leaving the
issuer'ssep1_resolved_atNULL. SinceIssuersNeedingSep1Refreshorders
sep1_resolved_at ASC NULLS FIRST, the 43,156 pubnet issuers with dead
domains permanently occupied the front of the queue — the refresh re-tried
the same dead domains every run and never made forward progress to the
~100 good issuers behind them (Circle, Aquarius, …).org_name/
org_verifiedcould therefore never populate at scale. New
MarkIssuerSep1Attemptedbumpssep1_resolved_aton failure (without
writing a payload), so a dead domain moves to the back of the queue and is
retried only on the next-older-thancadence; a later success overwrites
the payload as before./v1/pricelatency-burn incident (page severity) — root-caused + fixed.
LatestClosedVWAP1mForPair's "latest closed bucket" predicate was
bucket + INTERVAL '1 minute' <= now()— a function on the indexedbucket
column, so it's not sargable: TimescaleDB couldn't do chunk exclusion or
an ordered index scan, andmax(bucket)ran a full per-chunk partial
aggregate over the pair's ENTIRE prices_1m history (~13.7k rows/chunk × every
chunk back to 2015). Harmless while a pair was sparse; once
crypto:XLM/fiat:USD accrued dense history (CEX coinbase/kraken trades, from
~20:00 UTC 2026-06-19) it ballooned to ~446ms execution + 55k planner
buffers, driving the price p95 from ~50ms to ~400ms and the SLO burn /
sla-probe alerts. Two-part fix: (1) rewrote the non-sargable predicate to
the arithmetically-identicalbucket <= now() - INTERVAL '1 minute'
(execution 446ms → 26ms); (2) that still left ~280ms of planning time —
prices_1m has ~374 chunks andnow()only enables runtime chunk exclusion,
so the planner still enumerated every chunk. Added a LITERAL recent lower
bound (bucket >= <cutoff>, computed in Go) so the planner prunes old chunks
at PLAN time, collapsing planning to ~2ms. Net: ~390ms → ~8ms end-to-end.
Idle pairs (no closed bucket in the 14-day fast window) fall back to the
unbounded scan so the latest-closed-bucket contract is preserved. (The rc.133
fix to this function only bounded the sparse case; the sibling
ORDER BY bucket DESC LIMITreaders were verified unaffected.)/v1/contracts/{id}/wasmnow distinguishes a Stellar Asset Contract
(the built-in SAC behindnative, USDC, and every classic asset — among
the busiest contracts on the network) from a genuinely-uncaptured WASM
module (audit 2026-06-19 item 13). The reader found the SAC instance but,
since its executable isn't a WASM module, returned the generic
"unresolved" 404 — so the explorer wrongly said "resolves once a backfill
lands" for contracts that will never have WASM. SACs now return a distinct
contract-is-sac404 and the explorer shows "this is a Stellar Asset
Contract — no WASM." Because the busiest SACs (native XLM, USDC) were
deployed long ago and their instance entries also predate capture, SACs are
detected deterministically too — a contract id is matched against the
operatorsac_wrappersregistry AND the computed SAC derivations of the
native asset + every verified-catalogue classic asset — so
native/USDC/AQUA/… report "SAC, no WASM" without needing a captured
instance. (Real user contracts whose code was uploaded before the
entry-capture window still show the honest "not captured yet" state pending
the Phase-C backfill.)apiGetnow also surfaces the RFC-9457 problem
title/detailin thrown errors so clients can tell apart same-status
failure modes.- Class-filtered + unified
/v1/assetslistings now carryprice_usd.
?asset_class=crypto|stablecoin|fiat(and the explorer's
?asset_class=allfirst page) projected catalogue rows from the
price-less catalogue projection, so every row — even XLM — listed
price_usd: null(audit 2026-06-19 item 4). The sliced page now fills
the headline price through the same three-tier chain as the single-asset
/v1/assets/{slug}view, bounded to the page (not the whole catalogue)
so the unified first page doesn't fan a price computation over every
catalogue entry. Stellar-only tokens (AQUA, yXLM, SHX, …) that have no
global CEX/aggregator price fall back to their Stellar trades-derived
price (the same one the classic listing shows), so a class-filtered row
matches the classic asset row instead of listing null. /v1/assetsmarket_cap_usd+circulating_supplynow cover every
classic asset, not just the ~9 with a precise supply-pipeline figure
(audit 2026-06-19 item 4: market_cap was null for all 500). The precise
three-domainsupply_1dfigure is still preferred where it exists; the
long tail falls back to a broad circulating supply derived from the sum
of all (non-removed, positive) trustline balances per asset — the exact
definition of classic-asset circulating supply — read from the ClickHouse
lake via one cached GROUP BY (~0.5s, 10-min TTL + single-flight, kept off
the API hot path). market_cap =(circulating / 10^decimals) × price.
Assets without a price stay honestly null (no fabrication)./v1/protocols/{name}cold-path latency cut ~3× (audit 2026-06-19 item 8):
the three independent lake reads (daily series, event breakdown,
per-contract activity — ~5s each via the contract_id bloom index) ran
serially (~15s total); they now run concurrently and write disjoint view
fields, so the cold path is ~5s — comfortably under the 25s ceiling. The
"untyped" reconciling bucket is appended after the barrier (it needs the
series total). Repeat hits stay instant via the cache below./v1/protocols/{name}event breakdown now NAMES AMM swap/sync events
instead of lumping them into "untyped" (data-truth G4). Soroswap's events
are[String("SoroswapPair"), Symbol(name)], so the lake'stopic_0_sym
(which only captures a Symbol topic[0]) is empty and the real event name
lives in topic[1]. The breakdown now recovers it: when topic[0] isn't a
Symbol, it decodes topic[1]'s Symbol from the rawtopics_xdr— so
soroswap showsswap/sync/deposit/withdraw/skim(≈190k events that
were "untyped") and the untyped remainder collapses to ~0./v1/protocols/{name}is now served from a 60s per-server single-flight
cache, so concurrent requests no longer each re-run the ~15s lake scans
and peg CPU (compounding the 25s ceiling below)./v1/protocols/{name}can no longer peg CPU for minutes: the
lake-analytics + bespoke scans (~15s warm) had no request ceiling and
were observed running away to several minutes under concurrent load
(2026-06-19 incident). Added a 25s timeout; the enrichment helpers
degrade gracefully on cancellation. (The proper fix — a CAGG so these
are fast — is tracked in docs/archive/page-audit-2026-06-19/.)/v1/protocols/{name}event counts now reconcile (audit 2026-06-19
item 8).events_totalwas the typed-breakdown sum, which counts only
events whose topic[0] is a denormalized Symbol in the lake — so for
Soroswap it read 236 while the activity chart summed to ~200k (the
swap/sync events carry a non-Symbol topic[0]), and it could even fall
belowevents_24h.events_totalis now the unfiltered window total
(= the activity-series sum), andevent_breakdowncarries a synthetic
untypedbucket for the non-Symbol-topic'd remainder, so
sum(event_breakdown) == events_total == sum(activity_series). This also
fixes protocols (e.g. phoenix) showing an empty breakdown while the chart
had data.- MEV feed notionals no longer read ~$0 on real cycles: the arb scanner
read rawusd_volume(NULL for SDEX XLM/token + token/token legs), so
cycle notionals summed to ~$0. It now estimates each leg's USD value
from the XLM leg × current XLM/USD VWAP (the same fallback the markets
queries use), matching how the rest of the API values on-chain volume.
Applies to newly-detected cycles. - Chainlink divergence cross-check now actually runs — it had produced
zerodivergence_observationsrows ever (audit 2026-06-19). The
divergence Chainlink reference carried its own env-lessrpc_urlthat
fell back to a public RPC (eth.llamarpc.com) which now answers
eth_callwith a Cloudflare JS-challenge HTML page, so every
LookupPricefailed its JSON decode silently.CHAINLINK_RPC_URLnow
also overrides the divergence reference's endpoint (it already drove the
ingest poller), so one operator-provided RPC serves both. BTC/USD +
ETH/USD now cross-check against Chainlink mainnet feeds (~0.1% delta,
verified live on r1). /v1/issuers/{g}now populatesauth_required/auth_revocable/
auth_immutable/auth_clawbackfrom the on-chain AccountEntry flags
bitmask we already index (via the explorer'sAccountState), instead of
leaving them null when the dedicated SEP-1 flag resolver hasn't run. The
explorer's Auth-flags panel shows real values instead of "Not yet
resolved" for any issuer whose account we've observed.- Explorer per-page audit fixes (2026-06-19, frontend):
/assets/XLMshowed wrapped-XLM data (~330× wrong price) — a
scam "XLM" classic asset shared theXLMlisting slug.fetchCoin
now resolves XLM/native directly to the native asset.- Case-sensitive asset/embed routes — lowercase slugs (
/assets/btc,
/embed/asset/xlm) 404'd or rendered a half-emptyGlobalAssetView.
fetchCoinnow does a case-insensitive cache lookup and
generateStaticParamsemits both cases for every slug. /convert/{from}/{to}inverted rates ("1 USD = 1.15 EUR" was the
EUR→USD rate mislabeled) — now inverted correctly./sources"Last ingest" always "—" — the cursor venue lives in
sub_source(thesourcefield is the cursor type); both the index
and per-source panels now key on the venue./oraclesdropped the always-zero "24h updates" column (oracles
don't trade)./divergencesnow marks each reference Active / Configured / Planned
— only CoinGecko + Chainlink-HTTP are actual cross-checks; Reflector/
Redstone/Band are ingested as oracle feeds, not yet compared here, so
the page no longer implies all five are live.
/v1/pricelatency regression (caused a latency-burn incident
2026-06-19): the rc.131 cross-direction VWAP combine scanned a pair's
ENTIREprices_1mhistory (back to 2015) beforeLIMIT 1— ~1s warm,
~9s under load. Now it finds the latest closed bucket via an index
max()per direction (UNIONed), then point-reads + combines just that
bucket — bounded, ~250ms, same result.- CEX dust no longer pollutes OHLC high/low on the API. Sub-$0.001
streamed CEX fills — tiny integer amounts whosequote/baseis a
meaningless round fraction (1/8, 1/10, …) — are dropped at ingest
(newstellarindex_external_dust_dropped_total). Ingested, a single
such $0.00000001 fill set the unweighted/v1/ohlchigh/low
(e.g. an XLM/USD low wick of $0.125) while carrying ~zero real volume;
the candle body (volume-weighted VWAP) was always correct. Existing
dust was purged and the price CAGGs re-derived. - Flipped markets are no longer double-counted: XLM/USDC and USDC/XLM
(the SDEX decoder records both on-chain trade directions) now collapse
to a SINGLE market wherever pairs are read —/v1/markets,/v1/pools,
and the per-pair/v1/priceVWAP. Volume + trade count sum across both
directions; the price uses one canonical orientation (quote-rank: fiatstablecoin > XLM > token, so XLM/USDC quotes in USDC), and the VWAP
combines both directions over the latest closed bucket (so it uses full
liquidity, and returns a price even when the latest minute traded only
the flipped way). Query-time viacanonical.Orient— no data migration. - Charts now label their time axis in the viewer's local timezone
(intraday) instead of UTC, so the current bar lines up with the
viewer's wall clock instead of reading an hour "behind". Date labels
on daily/weekly views stay UTC (a daily bar is a UTC calendar bucket). - Source activity chart no longer shows a gap for the current hour:
source_volume_1hnow uses real-time aggregation (migration 0069),
so the in-progress (not-yet-materialized) hour is computed live
instead of reading as zero until the hour closed.
Changed
- Alerting fanout migrated from Slack to Discord. Both the R1
standalone Alertmanager config (configs/alertmanager/) and the
multi-host Ansible template now use Alertmanager-native
discord_configs. Discord incoming webhooks are locked to one
channel each, so the page tier and ticket tier take separate webhook
URLs (DISCORD_WEBHOOK_URL_PAGES/DISCORD_WEBHOOK_URL_ALERTSin
/etc/default/alertmanager-secrets;alertmanager_discord_webhook_url_pages
/_alertsvault vars for the Ansible role) — point both at the same
webhook for a single channel.apply.shnow drops each receiver's
config block independently when its URL is unset (marker-specific
strip, so one empty URL never collateral-removes the other Discord
receiver). Operator runbooks, the SEV playbook comms channels, and
pre-launch-check.shupdated accordingly. The oldSLACK_WEBHOOK_URL
/alertmanager_slack_*knobs are removed.