Skip to content

Dev - #2093

Open
pikonha wants to merge 31 commits into
mainfrom
dev
Open

Dev#2093
pikonha wants to merge 31 commits into
mainfrom
dev

Conversation

@pikonha

@pikonha pikonha commented Jul 29, 2026

Copy link
Copy Markdown
Member

Note

High Risk
Wallet/session decoupling and immediate stale-session gating change core auth UX and security boundaries; per-user Prometheus labels carry PII and require protected metrics and Grafana access.

Overview
This PR bundles several product and platform changes across the dashboard, offchain indexer, authful, user-api, and monitoring.

Wallet vs session: Connecting a wallet no longer implies signing in. Vote, delegate, and publish flows open RainbowKit directly without SIWE; header Connect opens the wallet picker instead of the login modal. Session-gated surfaces (drafts, API keys) still use the sign-in modal. useAuthSession and isWalletSessionStale hide wallet-born sessions immediately when the wallet disconnects or the account switches, instead of waiting on sign-out or force-disconnecting connected wallets without a session.

Snapshot / offchain governance: The indexer and API persist scoresTotal and quorum, with a historical metadata backfill job. The dashboard derives Passed, No quorum, and non-stale Ongoing states instead of mapping winning Snapshot proposals to on-chain-style Executed / queue labels.

API keys UX: Save-key modal layout, MCP section copy, animated client tabs, truncated names, usage dropdown/BlankSlate, shorter chart, and scrollable chart legend on mobile.

Validation metrics: Authful adds GET /tokens/active (usage from daily counters). User API exposes bearer-protected /metrics with per-user gauges (email/wallet identifiers), a Grafana User API Validation dashboard, and USER_API_METRICS_TOKEN required when Authful provisioning is enabled.

Monitoring: Removes the non-functional railway-exporter (scrape job, alerts, panels). Extends Prometheus retention; PR Grafana can use basic auth instead of Google OAuth.

Reviewed by Cursor Bugbot for commit 6b63c61. Configure here.

pikonha and others added 27 commits July 27, 2026 11:10
Delegating, voting and publishing a proposal are settled on-chain by the
wallet's own transaction signature, so they only ever needed a connected
account. They were routed through the sign-in modal instead, forcing a
SIWE message on anyone who just wanted to act on-chain.

Connecting and signing in are now separate:

- useWalletPrompt always opens the wallet picker, never the sign-in modal
- LoginProvider no longer force-disconnects a wallet that has no session,
  and no longer re-opens the sign-in modal when the account changes
- the header connect buttons (Anticapture and whitelabel) open the wallet
  picker directly
- the sign-in modal, still raised by the surfaces that need a server
  session (proposal drafts, API keys), labels its wallet option
  "Sign in with wallet"

Wallet-born sessions stay bound to their wallet: disconnecting or
switching accounts still signs them out, it just never prompts again.

Co-Authored-By: Claude Opus 5 <[email protected]>
…anges

Switching accounts on a SIWE session fired signOut() and left the old
session usable while the request was in flight, and indefinitely if it
failed: the header showed wallet B while drafts and API keys still acted
for wallet A.

Sign-out is now the cleanup, not the gate. isWalletSessionStale derives
the broken binding synchronously from wallet state, and useAuthSession
serves the session through it, so a mismatched or disconnected wallet
reads as no session from the render it happens. The API-key create and
delete modals live outside the auth branch, so they close on the account
change instead of submitting against the previous account.

Co-Authored-By: Claude Opus 5 <[email protected]>
- serve before initial metrics refresh so slow Authful can't keep /health down
- publish DB counts independently of Authful in metrics refresh
- derive daily-active from usage records, not lastUsedAt (survives Gateful cache hits)

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- export user_api_keys_created_total as a real counter incremented on
  creation instead of a mutable row count (cascade delete on account
  removal read as a counter reset / false increase() spike)
- guard MetricsSnapshotService.refresh against overlapping runs so a
  slow Authful can't clobber a newer snapshot or pile up fetches

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
It never emitted a single railway_service_* sample in either environment —
its account-level RAILWAY_API_KEY was rejected for the whole retained history
(~15d), so the only visible effect was a permanently firing RailwayExporterStale
critical alert and three blank Grafana panels.

Drops the scrape job, the three Railway alert rules, the three dashboard panels,
and the Dockerfile/railway.toml pair. Also removes the need for a workspace-wide
Railway API token in service env vars, and a build-time git clone of a
third-party repo.

Railway's own dashboard still shows per-service CPU/RAM/egress; only alerting
on them is gone.

Co-Authored-By: Claude <[email protected]>
chore(monitoring): remove railway-exporter
Save-key modal: default width on both steps, no key name or em-dash in
the description, 'Key' and 'MCP' titles at the same font size, no
'waiting for the first call' status, and animated code-block height
when switching client tabs. Keys table truncates long names with an
ellipsis. Usage section: key switcher is a dropdown with a max width,
blank slate for the empty state, and reduced height. Connect section
retitled 'MCP' with 'connect your AI agent' in the description. Modal
close button uses the small icon-button size.

ClickUp: 86ajr888u

Co-Authored-By: Claude Fable 5 <[email protected]>
…erflow

Long API key names are truncated to 24 chars in the usage chart series
(tooltip and legend), with the id suffix disambiguating names that
collapse to the same truncated label. The stacked-bar-chart legend now
scrolls instead of wrapping, so many/long series names no longer
overflow into the x-axis on narrow screens.

ClickUp: 86ajr888u

Co-Authored-By: Claude Fable 5 <[email protected]>
feat(dashboard): connect wallet without signing in
Returning DEFEATED conflated "nobody showed up" with "voted down" -
the enum and full ProposalItem styling for NO_QUORUM already exist.
Every caller of getProposalStatus/getProposalState feeds it the
onchain API's proposal.status, which never emits "passed" - only the
offchain path constructs ProposalStatus.PASSED directly.
The quorum row in ProposalInfoSection never appeared because the
Snapshot proposal was adapted with a hardcoded quorum of "0", even
though the API now indexes and exposes the real value.
The reconcile comment implied deletion was bounded to the 14-day
window, but backfillProposalMetadata walks and can delete proposals
across all history. Document that explicitly instead of hiding it
behind the word "backfill".
"Succeeded" was wrong - the code returns PASSED specifically to avoid
the on-chain "Pending Queue"/"succeeded" semantics for Snapshot
proposals.
@pikonha pikonha self-assigned this Jul 29, 2026
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
anticapture-storybook Ready Ready Preview Jul 29, 2026 10:03pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
anticapture (dev) Ignored Ignored Jul 29, 2026 10:03pm

Request Review

@railway-app

railway-app Bot commented Jul 29, 2026

Copy link
Copy Markdown

🚅 Deployed to the anticapture-pr-2093 environment in anticapture-infra

Service Status Web Updated (UTC)
prometheus ✅ Success (View Logs) Jul 29, 2026 at 10:02 pm
docs ✅ Success (View Logs) Web Jul 29, 2026 at 10:02 pm
otelcol ✅ Success (View Logs) Jul 29, 2026 at 10:02 pm
grafana ✅ Success (View Logs) Web Jul 29, 2026 at 10:02 pm
authful ✅ Success (View Logs) Web Jul 29, 2026 at 10:02 pm
loki ✅ Success (View Logs) Jul 29, 2026 at 10:02 pm
gateful ✅ Success (View Logs) Web Jul 29, 2026 at 10:02 pm
tempo ✅ Success (View Logs) Jul 29, 2026 at 10:02 pm
alertmanager ✅ Success (View Logs) Web Jul 29, 2026 at 10:01 pm
mcp ✅ Success (View Logs) Web Jul 29, 2026 at 10:01 pm
uniswap-indexer-offchain ✅ Success (View Logs) Jul 29, 2026 at 7:59 pm
ens-indexer-offchain ✅ Success (View Logs) Jul 29, 2026 at 7:59 pm
compound-indexer-offchain ✅ Success (View Logs) Jul 29, 2026 at 7:59 pm
shutter-indexer-offchain ✅ Success (View Logs) Jul 29, 2026 at 7:59 pm
gitcoin-indexer-offchain ✅ Success (View Logs) Jul 29, 2026 at 7:59 pm
aave-api ✅ Success (View Logs) Jul 29, 2026 at 6:35 pm
ens-relayer ✅ Success (View Logs) Jul 29, 2026 at 6:35 pm
gitcoin-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:35 pm
obol-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
nouns-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
compound-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
lil-nouns-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
uniswap-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
compound-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
gitcoin-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
nouns-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
aave-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
uniswap-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
fluid-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
shutter-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
obol-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
tornado-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
address-enrichment ✅ Success (View Logs) Web Jul 29, 2026 at 6:34 pm
fluid-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
shutter-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
tornado-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
ens-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
lil-nouns-api ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
ens-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
scroll-indexer ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
erpc ✅ Success (View Logs) Web Jul 29, 2026 at 6:34 pm
nodeful ✅ Success (View Logs) Jul 29, 2026 at 6:34 pm
scroll-api ✅ Success (View Logs) Jul 29, 2026 at 6:31 pm
user-api ✅ Success (View Logs) Web Jul 29, 2026 at 6:30 pm

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Revoked keys skew age buckets
    • Filtered revoked keys out of keysForActiveTokenIds so active user age buckets use only live keys' createdAt.

Create PR

Or push these changes by commenting:

@cursor push a7e8999e9f

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 6b63c61. Configure here.

}
const activeUsers = emptyActiveUsers();
for (const createdAt of newestKeyByUser.values()) {
activeUsers[ageBucket(now.getTime() - createdAt.getTime())] += 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revoked keys skew age buckets

Low Severity

user_api_active_users age buckets use each user’s newest API key by createdAt, but keysForActiveTokenIds returns revoked keys too. A user active on an older key can be bucketed by a newer revoked key’s age, so retention metrics misstate who is active today.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6b63c61. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b63c611bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.where(
and(
like(tokens.tenant, "user:%"),
gte(tokenUsageDaily.day, utcDay(since)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the São Paulo activity boundary

When User API requests activity since São Paulo midnight (03:00 UTC), utcDay(since) truncates that instant to the UTC calendar date. Because usage is stored in UTC-day buckets, the query also includes requests from 00:00–02:59 UTC, which belong to the previous São Paulo day; users active only during that interval are therefore reported as active today and their usage is overcounted. The stored bucket timezone must match the dashboard day boundary, or the data needs enough time precision to filter from the requested instant.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

🎨 UI Review

Scope: UI-only pass over apps/dashboard/ (wallet/session decoupling, Snapshot/offchain proposal status labels, API keys UX). Backend/infra changes (apps/api, apps/offchain-indexer, apps/authful, apps/user-api, monitoring) are out of scope for this review.

Path A — code-only + UX-heuristic review. No figma.com or ClickUp links were found in the PR body/comments, and a ClickUp search for the sub-feature names ("API keys UX", "wallet session", "Snapshot proposal status") returned nothing. No PR-specific dashboard preview was reachable either — the Vercel comment shows the anticapture (dev) deployment for this PR as Ignored/Skipped (only an unrelated Storybook build deployed as "Ready"), so findings below are reasoned from the diff, not a rendered preview.

Wallet/session decoupling

No issues found. isWalletSessionStale / useAuthSession cleanly centralize the "hide wallet-born session immediately on disconnect/account-switch" rule, and every gated surface (ApiKeysManager, useDrafts, GovernanceSection) was migrated consistently from useSession to useAuthSession. ConnectWallet, ConnectWalletCustom, and WhitelabelConnectWallet consistently route to openConnectModal rather than the login modal for on-chain-only actions. [Code-only]

Snapshot / offchain governance status

Status derivation (getOffchainProposalStatus) and the new PASSED / NO_QUORUM / non-stale ONGOING cases read correctly against the PR description, and are covered by the added offchainProposal.test.ts. [Code-only]

API keys UX

  • Nice-to-have: ApiKeysTable truncation (max-w-70 + title tooltip) and transform.ts's truncateLabel for chart series names are both solid, standard patterns — no notes.
  • Nice-to-have: UsageSection drops the desktop SegmentedControl for key filtering, collapsing all breakpoints onto the Select dropdown. This fixes overflow/truncation for long key names, but on desktop it trades away at-a-glance visibility of all key options for an extra click to open the dropdown — worth a quick gut-check with design that this is an acceptable trade rather than an oversight. [UX-heuristic]
  • Nice-to-have: ConnectAgentSection's new variant="modal" title block hand-rolls a <p>/<p> pair instead of reusing SectionTitle (used two lines below for variant="section" in the same file, with the same icon/title/description shape). Two parallel implementations of what looks like the same title anatomy — consider whether SectionTitle could take a compact-size prop instead. [UX-heuristic]

Design system / a11y

  • Nice-to-have: ModalHeader's close button shrinks from a 44px tap target (-m-2 size-11) to -m-1, with a comment reasoning "this header only renders on desktop (mobile uses the Drawer)". Confirmed in Modal.tsx — that's true — but the mobile/desktop split comes from useScreenSize().isMobile, which is a max-width viewport media query, not a touch-capability check. A touch device wider than the mobile breakpoint (e.g. a tablet in landscape, or a touch-screen laptop) will render the desktop Dialog/ModalHeader path and lose the enlarged tap target it had before. Low likelihood, worth a quick check with whoever owns the breakpoint constants. [UX-heuristic]
  • Confirmed BlankSlate's variant="title" used in UsageSection's empty state is an existing, valid design-system variant (not a one-off). [Code-only]

Summary

0 must-fix, 3 nice-to-have (all minor UX/DS-consistency notes, no functional blockers found). No Figma spec or ClickUp task was located for this PR, so nothing could be validated against a source-of-truth design — the notes above are heuristic-only.


Generated by Claude Code

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

🔍 Vercel preview: https://anticapture-ltvwi5shu-ful.vercel.app

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2d547dc5ae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +81 to +85
if (
data.AUTHFUL_URL &&
data.AUTHFUL_PROVISIONING_API_KEY &&
!data.USER_API_METRICS_TOKEN
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Provision the metrics token before making it startup-critical

Any existing environment with AUTHFUL_URL and AUTHFUL_PROVISIONING_API_KEY configured will now reject its environment and fail to start unless USER_API_METRICS_TOKEN is added during the same rollout. The tracked apps/user-api/.env.example and README still describe the Authful pair as sufficient, so following the repository's documented API-key configuration also produces an unbootable service; document and provision the new variable before enforcing this dependency.

Useful? React with 👍 / 👎.

Comment thread apps/user-api/src/services/metrics/index.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants