Skip to content

Observability & settlement-path hardening: alerting, health, metrics, auth, logging (C-091/110/111/112/113) - #242

Merged
kh0ra merged 4 commits into
mainfrom
feat/c112-alerting-c091-auth-c110-logging
Jun 9, 2026
Merged

kh0ra merged 4 commits into
mainfrom
feat/c112-alerting-c091-auth-c110-logging

Conversation

@mehmethayirli

Copy link
Copy Markdown
Member

Özet

M8 Observability & ops + M6 güvenlik kümesinden, on-chain blocker'a (#236)
bağımlı olmayan 5 backend issue'yu tek bir tutarlı **settlement-path gözlemlenebilirlik

  • kimlik doğrulama** dilimi olarak getiriyor. Tamamı flag-gated ve non-breaking —
    hiçbir varsayılan davranış değişmiyor; her yeni davranış bir env değişkeniyle açılıyor.

Akış olarak bütünlük: log (C-110) → metric (C-111) → health (C-113) →
alert (C-112), üstüne auth (C-091).

Issue Konu Durum
C-112 (#174) Operasyonel alerting Closes — alert seti + testler + 2 canlı koşul
C-113 (#176) Health checks (DB/RPC/program/crank) Closes — gerçek subsistem durumu
C-091 (#140) Mutating route'larda auth Refs — settlement+dispute yolu (her route değil)
C-110 (#170) Request correlation + tx-sig log Refs — aynı yol (tüm route'lar değil)
C-111 (#172) Metrics (settlement/dispute/fee/RPC) Refs — metrikler var; dashboard ayrı

2 commit · 10 dosya · tsc temiz · 316/316 unit test (+11) · eslint temiz · canlı devnet smoke.


C-112 · Operasyonel alerting — #174 M8

Ne: İnsan müdahalesi gereken koşullar için yapılandırılabilir bir webhook'a (Slack/
Discord/herhangi bir JSON endpoint) alert atan, opt-in ve asla throw etmeyen bir
alerting katmanı.

Nasıl / nerede:

  • lib/alerts.ts (yeni): sendAlert + formatAlert + 4 koşul helper'ı
    (alertCrankFailure, alertRpcDown, alertDbQuota, alertDisputeSpike).
    • ALERT_WEBHOOK_URL set değilse sendAlert no-op (false döner, fetch çağrılmaz).
    • Fetch hatası / non-2xx yanıt → yutulur (false), caller'a asla throw etmez —
      bir settlement path'i alerting çöktü diye kırılmamalı.
    • Formatlama saf, fetch inject edilebilir → webhook olmadan unit-testlenebilir.
  • Canlı wire'lı koşullar:
    • alertCrankFailure → app/api/cron/finalize catch'i ve HTTP
      app/api/jobs/[id]/finalize crank-failure catch'i (her ikisi fire-and-forget void).
    • alertDisputeSpike → her iki dispute route'u (/api/disputes +
      /api/jobs/[id]/dispute); son 10 dk içindeki dispute sayısı eşiği aşarsa atar.
  • tests/unit/alerts.test.ts (yeni, 11 test): format (severity/detail/fields, boş
    field eleme), no-op-when-unconfigured, posts-via-injected-fetch, fetch-throw→false,
    non-2xx→false, env'den okuma, 4 helper'ın her birinin doğru payload'u.

⚠️ Nüans — wiring durumu (overclaim yok):

  • Canlı (otomatik): crank-failure + dispute-spike.
  • Test edilmiş ama canlı sinyale bağlı değil: alertRpcDown (RPC failover'ın tüm
    endpoint'leri tükettiği noktaya bağlamak, merge'lenmiş C-064 callWithFailover'ında
    "exhausted" ile "non-retryable"ı ayıran bir refactor ister — o merged kodu bozmadım) ve
    alertDbQuota (doğru tetikleyicisi C-115'in dedup'lı db-size cron'u; metrics-scrape'e
    bağlamak her scrape'te spam üretirdi).
  • Yani: alert modülü + 4 koşul da implement + test edildi; 2'si otomatik atıyor,
    2'si tetikleyicisini bekleyen hazır helper.
  • dispute-spike COUNT'u ALERT_WEBHOOK_URL set'liyken çalışır — alerting kapalıyken
    ekstra DB sorgusu yok (sıcak yolda sıfır overhead).

C-113 · Health checks — #176 M8

Ne: /api/health'in gerçek subsistem durumunu yansıtması (DB, RPC, program, crank).

Nasıl / nerede (app/api/health/route.ts):

  • Mevcut database + schema + env kontrollerinin üzerine 3 yeni kontrol:
    • rpc — getServerConnection().getSlot() (Solana RPC erişilebilir mi).
    • program — getAccountInfo(PROGRAM_ID); account var ve executable mı.
      Kasıtlı olarak reachability kontrolü, correctness değil — deploy edilmiş ama buggy
      bir program da "reachable" raporlar; "program reachable"ın dürüst anlamı bu.
    • crank — settlement backlog'u: cron finalizer'ın işlediği tam sorgu
      (status=Delivered, challengeEndAt<=now). Backlog > tolerans ise crank ayak
      uyduramıyor demektir. Son finalize_payment Transaction zaman damgasını da raporlar.
      Tolerans HEALTH_CRANK_BACKLOG_MAX ile ayarlanır (varsayılan 0). Keyfi heartbeat
      yok
      — gerçek backlog sinyali.
  • Her RPC çağrısında 4sn timeout (Promise.race) → asılı bir RPC health'i kilitleyemez.
  • Graceful degradation: RPC down → program "skipped (rpc down)"; DB down → crank
    "skipped (db down)". Endpoint her zaman 200 + JSON döner; ok özetler.

Doğrulandı (canlı devnet smoke, DB kasıtlı kapalı):

"rpc":     { "ok": true,  "detail": "slot=468110796" }
"program": { "ok": true,  "detail": "deployed + executable (5hstj5gr…)" }
"crank":   { "ok": false, "detail": "skipped (db down)" }   ← doğru degradation

RPC + program kontrolleri gerçek devnet'e karşı çalışıyor; DB kopukken crank/db temiz
biçimde "skipped"e düşüyor.


C-091 · Mutating route'larda auth — #140 M6

Ne: Değer/durum değiştiren route'larda doğrulanmış cüzdan imzası veya API key zorunluluğu.

Nasıl / nerede:

  • requireAuth(req) (merge'lenmiş lib/require-auth — AUTH_ENFORCED set değilse
    no-op) eklendi: tam para + dispute yolu —
    POST /api/jobs (create) → accept → submit → cancel → finalize +
    POST /api/jobs/[id]/dispute. (/api/disputes zaten feat(backend): credit-market tests, mutating-endpoint auth (flag-gated), tx-sig logging #234'te wire'lıydı.)
  • Handler tepesine 2 satırlık, body'ye dokunmayan ekleme (method+path+ts bağlar; route
    kendi req.json()'ını sonra okumaya devam eder).

⚠️ Nüanslar:

  • finalize tasarımı gereği permissionless (challenge süresi sonrası herkes
    settle edebilir = sansür direnci). Auth opt-in olduğu için kırılmıyor; AUTH_ENFORCED
    açılırsa frontend imzalar, keeper bot API key sunar — koda yorum olarak düştüm.
  • Public demo oyunları (battle/arena/autonomous/a2a vb.) kasıtlı auth'suz —
    dokunulmadı.
  • Bu yüzden Refs, Closes değil: "her mutating route" tam karşılanmıyor; ikincil
    hassas route'lar (profile/reviews/keys/referral/claims-list/agents-register) takip işi.

C-110 · Request correlation + on-chain tx-sig logging — #170 M8

Ne: Her isteğin correlate edilebilmesi; on-chain tx sig'lerinin loglanması.

Nasıl / nerede:

  • log.forRequest(req) (route/method/request_id çıkaran, merge'lenmiş lib/logger) +
    üretilen tx sig'in loglanması, C-091 ile aynı yola eklendi:
    create_job / accept / submit / cancel / finalize_payment / dispute raised.
  • Örn. finalize: reqLog.info("finalize_payment settled", { jobId, txHash }).

⚠️ Nüans — Refs, Closes değil: lifecycle/para yolu kapsandı; bazı route'lar
(log.forRequest'i zaten kullanan health/faucet/claims-buy/admin dışında) hâlâ
eklenmeyi bekliyor. "Tüm route'lar" daha geniş bir süpürme.


C-111 · Metrics — #172 M8

Ne: /api/metrics'te gerçek sayaçlar (settlement volume, dispute rate, fee accrual,
RPC health).

Nasıl / nerede (app/api/metrics/route.ts, mevcut Prometheus exposition'a eklendi):

  • covenant_settlement_volume_usdc — Finalized job amount'larının toplamı (gerçek
    settlement'ı yansıtır; sadece settle olanlar sayılır).
  • covenant_fees_accrued_usdc — settle olan hacim × PROTOCOL_FEE_BPS. Gerçek settle
    edilmiş amount'lardan türetilir
    (uydurma değil); settlement 0 ise 0.
  • covenant_disputes_total + covenant_disputes_by_resolution{resolution} +
    covenant_dispute_rate (dispute/job oranı, 0..1).
  • covenant_rpc_up + covenant_rpc_slot + covenant_rpc_latency_ms — canlı getSlot
    probe'u (4sn timeout).

Doğrulandı (canlı devnet smoke):

covenant_rpc_up 1
covenant_rpc_slot 468110802
covenant_rpc_latency_ms 155
covenant_settlement_volume_usdc 0   ← DB kapalı → dürüstçe 0
covenant_disputes_by_resolution{resolution="none"} 0   ← boş-fallback çalışıyor

⚠️ Nüans — Refs, Closes değil: Metrikler gerçek aktiviteyi yansıtıyor ama issue
açıklamasındaki "wire a dashboard" (Grafana) bu PR'da yok — ayrı ops adımı. Ayrıca
#236 redeploy'a kadar gerçek on-chain settlement 0'dır; metrikler bunu dürüstçe
yansıtır (uydurma hacim yok).


Doğrulama

cd app
npx tsc --noEmit                        # temiz
npx tsx --test tests/unit/*.test.ts     # 316/316 (+11 alerts)
npx eslint <değişen dosyalar>           # temiz
# canlı: next dev + curl /api/health & /api/metrics → yukarıdaki gerçek devnet çıktıları

Bu PR'da olmayanlar (dürüst sınır)

  • C-115 (C-115: DB transfer-cost guardrails (polling, caching, indexes) #180) DB transfer-cost guardrails — alertDbQuota'yı dedup'lı bir db-size
    cron'una bağlama + query caching/index + polling ≥30s. Ayrı PR (frontend + olası
    migration + dedup'lı cron parçaları var).
  • alertRpcDown'ı RPC failover'ına canlı bağlama (C-064 refactor'u gerektirir).
  • C-091/C-110'un kalan ikincil route'lara yayılması; C-111 için Grafana dashboard.

Closes #174
Closes #176

Refs #140 · Refs #170 · Refs #172

mehmethayirli and others added 2 commits June 9, 2026 02:06
…e settlement path (C-091, C-110)

Three M-tier backend items, all flag-gated and non-breaking.

C-112 (#174) — operational alerting
- lib/alerts.ts: sendAlert + formatAlert + 4 condition helpers
  (crank-failure, rpc-down, db-quota, dispute-spike). Opt-in via
  ALERT_WEBHOOK_URL (no-op when unset); never throws into a caller
  (a settlement path must not fail because alerting is down); pure
  formatting + injectable fetch -> unit-testable offline.
- Wired live: crank-failure -> cron/finalize + HTTP finalize catches;
  dispute-spike -> both dispute routes (gated on ALERT_WEBHOOK_URL so
  the extra COUNT only runs when alerting is configured).
- tests/unit/alerts.test.ts (11): format, no-op-when-unconfigured,
  posts-via-injected-fetch, swallow-errors, all 4 helpers.

C-091 (#140) — auth on mutating routes
- requireAuth(req) on the full create->accept->submit->cancel->finalize
  path + both dispute routes. No-op unless AUTH_ENFORCED is set.
- finalize stays permissionless by design: auth is opt-in; under
  enforcement the frontend signs and a keeper bot uses an API key.

C-110 (#170) — request correlation + tx-sig logging
- log.forRequest(req) + on-chain tx sig logged on the same path
  (create_job / accept / submit / cancel / finalize / dispute).

tsc clean; 316/316 unit tests; eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ettlement/dispute/fee/RPC metrics (C-111)

Completes the M8 observability cluster on top of the alerting/logging
commit: /api/health now reflects real subsystem status, and /api/metrics
exposes the real settlement/dispute/fee/RPC figures.

C-113 (#176) -- /api/health reflects real subsystem status
- Added RPC reachability (Solana getSlot), on-chain program reachability
  (getAccountInfo(PROGRAM_ID) executable check -- reachability, not
  correctness), and crank liveness (overdue finalize backlog = Delivered
  jobs past challengeEndAt; tolerance via HEALTH_CRANK_BACKLOG_MAX).
- 4s per-call timeout so a hung RPC can't stall the check; each failure
  degrades gracefully (rpc down -> program skipped; db down -> crank skipped).
- Devnet smoke: rpc ok (slot=468110796), program ok (deployed+executable),
  crank/db degrade cleanly when DB is unreachable.

C-111 (#172) -- /api/metrics reflects real on-chain activity
- covenant_settlement_volume_usdc  (sum of Finalized job amounts)
- covenant_fees_accrued_usdc       (settled volume * PROTOCOL_FEE_BPS -- derived
                                    from real settled amounts, 0 when 0)
- covenant_disputes_total / _by_resolution / covenant_dispute_rate
- covenant_rpc_up / _slot / _latency_ms (live getSlot probe)
- Devnet smoke: rpc_up=1, slot/latency real; DB-derived gauges 0 with DB down
  (honest -- no fabricated settlement while the program is unredeployed, #236).

tsc clean; 316/316 unit tests; eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 5c06347

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
covenant Ready Ready Preview, Comment Jun 9, 2026 3:38pm

mehmethayirli and others added 2 commits June 9, 2026 18:33
…es + polling (C-115) (#243)

Stacked on the observability PR (uses alertDbQuota from lib/alerts). Makes the
Neon quota incident unable to recur silently, and trims the polling/query load
that drives transfer cost.

Alert before quota (the core)
- lib/db-quota.ts: pure quotaUsageRatio + quotaLevel + env config, plus
  checkDbQuota (injectable size-probe + alert sink, never throws) and a
  pg_database_size probe. Opt-in via DB_QUOTA_LIMIT_BYTES (no-op when unset);
  warn/critical at DB_QUOTA_WARN_RATIO / DB_QUOTA_CRIT_RATIO (default 0.80/0.95).
- app/api/cron/db-quota: CRON_SECRET-secured GET (same posture as the other
  crons) that probes size, always logs the level, and fires alertDbQuota at
  warn+. The cron cadence is the alert rate-limit (schedule it hourly).
- tests/unit/db-quota.test.ts (17): ratio/level/config + every checkDbQuota
  path (no-op, under/at/over threshold, probe-failure, observer).

Cut transfer cost
- Polling: home (15s->30s) and settlement (10s->30s) data polls bumped to >=30s
  per the AC. (1s UI countdown/clock timers left alone — they hit no DB.)
- Caching: /api/stats (home) wrapped in memoize (30s TTL) — collapses the 5
  queries-per-poll into one set per window. (/api/settlement/stats already
  memoized.)
- Index: @@index([updatedAt]) on Job — used by 10 hot "recent activity" orderBy
  queries and previously unindexed. (status / challengeEndAt / etc. already
  indexed.)

Verified: prisma schema valid; tsc clean; 333/333 unit tests (+17); eslint clean;
live smoke of the cron (no-auth / wrong-secret -> 401, Bearer -> 200 with the
correct result JSON, probe-fail degrades to level=ok).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	app/app/api/jobs/route.ts
@kh0ra
kh0ra merged commit 413ddd9 into main Jun 9, 2026
9 of 12 checks passed
@kh0ra
kh0ra deleted the feat/c112-alerting-c091-auth-c110-logging branch June 9, 2026 15:38

This branch was successfully deployed

1 active deployment
Preview — 5c06347d Deployed Jun 9, 2026 by vercel[bot]
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.

C-113: Health checks (DB, RPC, program reachable, crank liveness) C-112: Alerting (crank failures, RPC down, DB quota, dispute spike)

2 participants