-
Notifications
You must be signed in to change notification settings - Fork 8
fix(keeper): wire health monitors to real outcomes instead of placeholder data #369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { createServiceMonitors } from "@percolatorct/shared"; | ||
|
|
||
| /** | ||
| * BUG-110: standard health monitors (rpc/scan/oracle/db), surfaced in | ||
| * /health's `monitors` sub-object. Factored out of index.ts so crank.ts and | ||
| * oracle.ts can record real outcomes without a circular import on index.ts. | ||
| * Each monitor is only as accurate as its wiring — see the recordSuccess/ | ||
| * recordFailure call sites in index.ts (rpc), crank.ts (scan, db), and | ||
| * oracle.ts (oracle). | ||
| */ | ||
| export const monitors = createServiceMonitors("Keeper"); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ import { | |
| txLandTimeSeconds, | ||
| } from "../lib/metrics.js"; | ||
| import type { AccountLoader } from "../lib/account-loader.js"; | ||
| import { monitors } from "../lib/service-monitors.js"; | ||
| import { keeperSend, sharedBudget } from "../lib/keeper-send.js"; | ||
| import { sharedTxQueue } from "../lib/tx-queue.js"; | ||
| import { parseV17RiskParams, V17_RISK_PARAMS_MIN_DATA_LEN } from "../lib/v17-risk.js"; | ||
|
|
@@ -1189,6 +1190,10 @@ export class CrankService { | |
| .in("slab_address", slabAddresses); | ||
| if (error) { | ||
| logger.warn("Supabase market metadata query error", { error: error.message }); | ||
| // BUG-110: record so /health's monitors.db reflects real DB outcomes. | ||
| monitors.db.recordFailure(error.message).catch(() => {}); | ||
| } else { | ||
| monitors.db.recordSuccess().catch(() => {}); | ||
| } | ||
| if (data) { | ||
| const base58Re = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; | ||
|
|
@@ -1207,6 +1212,7 @@ export class CrankService { | |
| logger.warn("Failed to fetch market metadata from Supabase", { | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| monitors.db.recordFailure(err instanceof Error ? err.message : String(err)).catch(() => {}); | ||
| } | ||
|
|
||
| const discoveredKeys = new Set<string>(); | ||
|
|
@@ -2065,8 +2071,12 @@ export class CrankService { | |
| }); | ||
| } | ||
| } | ||
| // BUG-110: the cycle (discovery + crank pass) completed without | ||
| // throwing — record so /health's monitors.scan reflects real outcomes. | ||
| monitors.scan.recordSuccess().catch(() => {}); | ||
| } catch (err) { | ||
| logger.error("Crank cycle failed", { error: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : undefined }); | ||
| monitors.scan.recordFailure(err instanceof Error ? err.message : String(err)).catch(() => {}); | ||
|
Comment on lines
+2074
to
+2079
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Don’t treat “didn’t throw” as a successful scan cycle. Lines 1122-1145 and 1157-1168 inside 🤖 Prompt for AI Agents |
||
| } finally { | ||
| this._cycling = false; | ||
| // H4: disarm the watchdog on natural recovery so a transient slow | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import { | |
| import { eventBus, createLogger, getErrorMessage, sendWarningAlert, sendCriticalAlert } from "@percolatorct/shared"; | ||
| import { isMainnet } from "../config/network.js"; | ||
| import { oraclePushCountTotal, oracleStalenessSeconds } from "../lib/metrics.js"; | ||
| import { monitors } from "../lib/service-monitors.js"; | ||
|
|
||
| const logger = createLogger("keeper:oracle"); | ||
|
|
||
|
|
@@ -184,8 +185,14 @@ export class OracleService { | |
| }); | ||
| clearTimeout(timeoutId); | ||
|
|
||
| if (!res.ok) return null; | ||
|
|
||
| // BUG-110: record real connectivity outcomes so /health's monitors.oracle | ||
| // reflects whether the external price feeds are actually reachable. | ||
| if (!res.ok) { | ||
| monitors.oracle.recordFailure(`DexScreener HTTP ${res.status}`).catch(() => {}); | ||
| return null; | ||
| } | ||
| monitors.oracle.recordSuccess().catch(() => {}); | ||
|
|
||
|
Comment on lines
+188
to
+195
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Avoid racing two upstreams into one
Also applies to: 228-228, 262-268, 284-284 🤖 Prompt for AI Agents |
||
| const json = (await res.json()) as DexScreenerResponse; | ||
|
|
||
| // M7: Validate BEFORE caching — don't cache bad responses that would | ||
|
|
@@ -218,6 +225,7 @@ export class OracleService { | |
| mint, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| monitors.oracle.recordFailure(err instanceof Error ? err.message : String(err)).catch(() => {}); | ||
| return null; | ||
| } | ||
| } | ||
|
|
@@ -251,8 +259,13 @@ export class OracleService { | |
| }); | ||
| clearTimeout(timeoutId); | ||
|
|
||
| if (!res.ok) return null; | ||
|
|
||
| // BUG-110: see fetchDexScreenerPrice — same connectivity signal for Jupiter. | ||
| if (!res.ok) { | ||
| monitors.oracle.recordFailure(`Jupiter HTTP ${res.status}`).catch(() => {}); | ||
| return null; | ||
| } | ||
| monitors.oracle.recordSuccess().catch(() => {}); | ||
|
|
||
| const json = (await res.json()) as JupiterResponse; | ||
| const priceStr = json.data?.[mint]?.price; | ||
| if (!priceStr) return null; | ||
|
|
@@ -268,6 +281,7 @@ export class OracleService { | |
| mint, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| monitors.oracle.recordFailure(err instanceof Error ? err.message : String(err)).catch(() => {}); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Expose the new
dblane in/health.This re-export makes
monitors.dbavailable, but Lines 658-662 still serialize onlyrpc,scan, andoracle. The DB monitor updates added insrc/services/crank.tsnever reach/health, so DB outages remain invisible despite this PR’s stated goal.🤖 Prompt for AI Agents