You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Projector decodes each lake event exactly once (stake-buffer
investigation 2026-08-11): the append-log lake is read without FINAL,
so re-ingested duplicate rows reached every decoder — absorbed by
keyed sinks for stateless sources, but CORRUPTING for buffered
decoders (phoenix's multi-event correlation: a duplicate re-opens a
completed group and can cross-assign fields between a mixed op's
legs — the 616 bond/unbond class). An adjacent-identity guard at the
single decode entry point (mirroring the completeness reconcile's
guard) now skips exact re-deliveries; events_emitted stops
over-counting duplicates.
Added
Passkey (WebAuthn) sign-in for the dashboard: six new
endpoints under /v1/auth/passkey/ — begin-login / finish-login (anonymous, usernameless discoverable-credential
flow; finish mints the SAME session cookie the email-code flow
does, via the shared session-mint path), begin-register / finish-register (session-gated; resident key required so the
credential can sign in usernameless), and credentials
(GET list + DELETE {id}, session-gated, owner-scoped). Server
is github.com/go-webauthn/webauthn v0.17.4; RP ID/origin derive
from the existing api.dashboard.base_url. Ceremony state rides
a 5-minute HMAC-signed HttpOnly cookie (purpose-bound so a
registration challenge can't finish a login); a sign-count
regression (possible cloned authenticator) refuses the login and
logs. Storage is the new webauthn_credentials table (migration
0140, additive). Explorer: "Sign in with a passkey" on /signin
(feature-detected) + a Passkeys list/add/remove card on
/dashboard/settings. OpenAPI paths + all three generated
artifacts refreshed; SDK triage recorded in uncoveredOperations.
Security
Dashboard 6-digit sign-in codes are no longer derivable from
the database (parked audit finding, aggregate+dashboardauth
cold audit 2026-08-03): the code was an unkeyed public function
of magic_link_tokens.token_hash (base32 of its first 4 bytes),
so any Postgres read — SQL injection elsewhere, a stolen backup —
yielded every in-flight sign-in code directly, no brute force
needed, and with it a session for any address the reader could
trigger a login for. The code is now HMAC-SHA256(server_secret, token_hash) reduced to 6 digits —
same UX, same storage, one derivation swapped; the secret lives
in config/env (api.dashboard.code_secret_env, default STELLARINDEX_DASHBOARD_CODE_SECRET), never in Postgres. With
the env unset the API falls back to a random per-process secret
(still keyed; in-flight codes just don't survive a restart —
they live 15 minutes and the magic link is unaffected). Deploy
note: codes emailed before the deploy stop verifying for their
remaining TTL; links keep working.
onboarding path**: one unauthenticated POST (empty body fine;
optional name + contact-only email, never verified) creates a
free-tier platform account and mints its first Postgres-backed API
key, returning {account_id, api_key, key_id, key_prefix, tier, limits} with the plaintext shown once. Rides the same per-IP
signup throttle as /v1/signup (shared budget → 429) and the
signup Content-Type CSRF gate. OpenAPI path + all three generated
artifacts refreshed (docs-api, docs-postman, web-generate-api);
SDK triage recorded in uncoveredOperations; agent-facing
walkthrough at docs/agent-onboarding.md.
Changed
Tier model collapsed to anon / free / partner (follow-up
to the Stripe removal — the platform is free). free is every
registered account's default, anchored to the old Starter numbers
(1000 req/min, 1M req/month, 25 keys, 10 webhooks, 25 price
alerts); partner is staff-set per-account limits via the existing
PATCH /v1/admin/accounts/{id} override + key-clamp path, with the
old Enterprise numbers as ceilings when no override is set
(100k req/min, 1B req/month, 250 keys, 100 webhooks, 1000 alerts); anon documents the unauthenticated 60/min per-IP baseline.
Legacy stored tier strings map in code
(platform.Tier.Canonical: starter→free,
pro/business/enterprise→partner; unknown fails closed to free) and
writes fold back to CHECK-legal strings
(platform.Tier.StorageValue) — migrations untouched. The admin
PATCH accepts both vocabularies and canonicalises.
Removed
Stripe/billing integration removed — the platform is free
(operator decision 2026-08-10: anonymous access, free accounts,
staff-set partner limits; no payments). Deleted the POST /v1/webhooks/stripe endpoint (handler + route + OpenAPI path
generated artifacts), [api.stripe] config
(STELLARINDEX_STRIPE_WEBHOOK_SECRET), platform.BillingStore / Subscription / StripeEvent and their Postgres store, Account.StripeCustomerID + GetByStripeCustomerID, the stellarindex_stripe_platform_sync_errors_total + stellarindex_stripe_dead_letters_open metrics with both alert-rule
trees and their runbooks, and paid-plan copy in the explorer
(pricing/signup/company/dashboard now describe free access).
The shared tier-clamp machinery the admin PATCH /v1/admin/accounts/{id} path uses survives in internal/api/v1/keybudgets.go (StripeKeyManager → SelfServiceKeyManager). Migrations are untouched — historical stripe_* columns/tables stay in place, unused.
Fixed
Contract WASM view resolves pre-capture contracts ("this
contract's on-chain WASM isn't in the captured ledger window yet",
operator report 2026-08-11): the instance→hash hop now reads the
genesis-complete contract_instance_changes index first, so any
contract whose instance was ever written resolves its current
executable (or SAC verdict) regardless of the live-capture window;
the code-bytes hop was already lake-complete (r1-measured: all
4,534 contract_code keys present). Legacy read remains the fallback.
Trade USD valuation: divergent-leg cross-check (fake-XMR
incident 2026-08-11): an attacker planted an INDUSX/XLM bridge rate
for the cost of the $0.01 dust floor and two no-XLM-leg trades were
stamped ~$91M each off the poisoned quote-side rate (real value
<$0.01 — a $182M fake spike in the SDEX volume series). The FX tier
now values BOTH legs through the resolver when possible and stores
the SMALLER when they disagree beyond 10× — inflating a print now
requires pumping both legs' markets with real value. The two
poisoned rows re-derive to honest values via the generation-guarded
corrective path.