Skip to content

feat(x402): add last-payment-success-seconds gauge + recording rule for settlement log#550

Open
bussyjd wants to merge 1 commit into
mainfrom
feat/last-payment-success-gauge
Open

feat(x402): add last-payment-success-seconds gauge + recording rule for settlement log#550
bussyjd wants to merge 1 commit into
mainfrom
feat/last-payment-success-gauge

Conversation

@bussyjd
Copy link
Copy Markdown
Collaborator

@bussyjd bussyjd commented May 25, 2026

Why

The My Purchases chevron drawer in the frontend currently shows:

SETTLEMENT LOG: The controller doesn't persist per-request settlements on the PurchaseRequest. Wire the verifier's obol_x402_verifier_charged_requests_total or add an obol_x402_verifier_last_payment_success_seconds gauge to render the table the design canvas shows here.

That's a self-spec. The gauge already lands on the verifier success branch (internal/x402/verifier.go:206 and :261, right next to chargedRequests.With(labels).Inc()), registered alongside the existing counters in internal/x402/metrics.go and tested in internal/x402/verifier_test.go. The missing piece — and the only change in this PR — is the matching Prometheus recording rule so the frontend can query age-since-last-settlement without joining against the buyer sidecar or scraping on-chain receipts.

Schema

  • Metric: obol_x402_verifier_last_payment_success_seconds
  • Type: Gauge
  • Labels: offer_namespace, offer_name, chain, asset_symbol (matches prometheusLabels())
  • Value: Unix timestamp in seconds (Prometheus convention), stamped via SetToCurrentTime() in the same success branch that bumps obol_x402_verifier_charged_requests_total. Already gated by pruneSeriesNotIn so deleted offers stop emitting stale timestamps.

Recording rule

Added to x402.recording group in internal/embed/infrastructure/base/templates/x402-prometheus-rules.yaml:

- record: x402:last_payment_age_seconds:by_offer_chain
  expr: |
    time() - max by (offer_namespace, offer_name, chain) (
      obol_x402_verifier_last_payment_success_seconds
    )

Rationale

  • No clamp_min / no constant floor. Until an offer settles at least once, the gauge has no series for it — max(...) returns no samples and the recorded series is simply absent for that offer. The frontend renders "no settlements yet" instead of a misleading "X seconds since 1970".
  • Age computed at query (= rule-evaluation) time via time() - max(gauge), so a single counter .Inc() plus one gauge .Set(now) keeps the per-request hot path cheap (no per-request timer math at scrape time).
  • max by (...) collapses asset_symbol — a single (offer, chain) almost always pins one asset, but max is safe even if a route re-pins (USDC -> OBOL) because the most recent settlement wins.
  • Pod restart resilience. A verifier restart clears the in-process gauge but Prometheus TSDB retains the last sample, so the rule keeps producing a sensible age across rollouts.

Test plan

  • go build ./... — clean
  • go test ./internal/x402/... ./internal/serviceoffercontroller/... — pass (existing verifier_test.go covers the gauge wiring at lines 880-979 and the prune semantics at 1016-1053)
  • Helm template smoke (reproduced CI's helm-template-smoke.yml flow locally with OLLAMA_HOST_IP=127.0.0.1, CLUSTER_ID=ci-helm-smoke, dataDir=/data, network=mainnet) — renders cleanly, new recording rule appears in the rendered output verbatim
  • helm lint stand-in — clean

Frontend follow-up

Paired PR in obol-stack-front-end will query x402:last_payment_age_seconds:by_offer_chain and render the My Purchases settlement table off the recorded series. No more "wire one of these two metrics" placeholder copy.


Do NOT merge — human review required per feedback_main_merge_gates.md.

…or settlement log

The `obol_x402_verifier_last_payment_success_seconds` gauge already lands
on the verifier success branch (see internal/x402/verifier.go:206 and :261,
right next to chargedRequests.Inc()). Add the matching recording rule so
the frontend My Purchases drawer can render "last settlement: 12s ago"
labels without joining against the buyer sidecar or chasing receipts.
@bussyjd bussyjd mentioned this pull request May 25, 2026
10 tasks
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.

1 participant