Skip to content

fix(credit-score): detect and surface stale scores when scoring config changes (#573)#591

Open
victor-olamide wants to merge 2 commits into
astera-hq:mainfrom
victor-olamide:fix/credit-score-config-version
Open

fix(credit-score): detect and surface stale scores when scoring config changes (#573)#591
victor-olamide wants to merge 2 commits into
astera-hq:mainfrom
victor-olamide:fix/credit-score-config-version

Conversation

@victor-olamide

Copy link
Copy Markdown
Contributor

Summary

Fixes #573 — credit score version not bumped on scoring config change, leaving stale scores indistinguishable from current ones.

Contract (already merged into this branch):

  • Added config_version: u32 to ScoreCoreConfig (initialized to 1)
  • set_scoring_config() enforces that callers must provide an incremented score_version, so every config change is detectable
  • New CreditScoreResponse struct returned by get_credit_score() exposes score_version (config version when score was last computed), config_version (currently active), and the derived is_stale bool — consumers get all three in a single call with no extra round-trip
  • 5 targeted tests: initial state, score current after payment, score stale after config bump, score refreshes after new payment under new config, new SMEs always start current

Frontend (this PR):

  • frontend/src/generated/credit_score.ts — added CreditScoreResponse interface; updated get_credit_score return type from CreditScoreData to CreditScoreResponse
  • frontend/lib/stellar.ts — exported CREDIT_SCORE_CONTRACT_ID from NEXT_PUBLIC_CREDIT_SCORE_CONTRACT_ID
  • frontend/lib/contracts.ts — added getCreditScoreStatus(sme) helper and re-exported CreditScoreResponse
  • frontend/components/CreditScore.tsx — added isStale?: boolean prop; renders a yellow warning banner ("Score may be outdated — scoring parameters were updated after this score was last computed. It will refresh automatically on your next invoice payment.") when isStale is true
  • frontend/app/dashboard/page.tsx — fetches getCreditScoreStatus on mount (keyed to wallet.address) and passes isStale to <CreditScore />

Also includes a security fix for the webhook test endpoint (SSRF hardening).

Test plan

  • Run cargo test -p credit_score — all 5 new staleness tests pass
  • Deploy updated contract to testnet; bump score_version via set_scoring_config; confirm existing SME score shows stale banner on dashboard
  • Record new payment for that SME; confirm banner disappears on next page load
  • Confirm new SME (no history) never sees the stale banner even after a config bump
  • npx tsc --noEmit passes clean in frontend/

Replace the permissive isValidUrl check (which allowed http:// and any
host) with isSafeWebhookUrl: HTTPS-only, RFC-1918/loopback/link-local
block via DNS resolution, IPv4-mapped IPv6 decoding, cloud metadata
hostname pre-block, and redirect:error to prevent open-redirect SSRF.
…ersion (astera-hq#573)

Add CreditScoreResponse type to the generated TypeScript bindings,
exposing config_version and is_stale alongside the existing score
fields. Wire a getCreditScoreStatus helper (lib/contracts.ts) that
calls the updated get_credit_score view. The dashboard now fetches
staleness on mount and passes isStale to CreditScore.tsx, which
renders a yellow warning banner when the stored score was computed
under an older scoring config than the one currently active.

Closes astera-hq#573
@victor-olamide victor-olamide force-pushed the fix/credit-score-config-version branch 2 times, most recently from 325fd9c to 2a1e2df Compare June 21, 2026 11:53
@sanmipaul

Copy link
Copy Markdown
Contributor

This is the complete fix for #573, contract changes plus full frontend wiring. The SSRF hardening on the webhook endpoint is a welcome bonus. A few things before merge:

Please fix before merge

Missing cleanup in the staleness useEffect — if the dashboard unmounts before getCreditScoreStatus resolves, setIsScoreStale fires on an unmounted component (React 18 warning). Add a cancelled flag:

useEffect(() => {
  if (!wallet.address) return;
  let cancelled = false;
  getCreditScoreStatus(wallet.address).then((result) => {
    if (!cancelled && result) setIsScoreStale(result.isStale);
  });
  return () => { cancelled = true; };
}, [wallet.address]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: credit score version not bumped on scoring config change — stale scores appear current

2 participants