Skip to content
Merged

Dev #2118

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
77acfc3
chore: release step wait for gateful to be ready
pikonha Aug 11, 2026
9a3ccf2
fix: erpc cache alert from 50% to 25% over 1 hour
pikonha Aug 11, 2026
e4222eb
feat(dashboard): flag proposals with reviewed calldata
pikonha Aug 11, 2026
e1563ae
chore: erpc external consumers config
pikonha Aug 11, 2026
20c9142
fix(dashboard): pick the longest matching calldata review slug
pikonha Aug 11, 2026
ced8718
revert: drop erpc cache alert tuning from this branch
pikonha Aug 11, 2026
b7875fa
chore(infra): add production eRPC config for external node consumers
pikonha Aug 11, 2026
4672efe
fix(monitoring): scope the 12h repeat interval to the cache alert
pikonha Aug 11, 2026
ab1aaca
Merge pull request #2114 from blockful/fix/erpc-cache-alert
pikonha Aug 11, 2026
2e6fe58
Merge remote-tracking branch 'origin/dev' into feat/proposal-calldata…
pikonha Aug 11, 2026
eceea31
fix(dashboard): only badge calldata reviews with an unambiguous match
pikonha Aug 11, 2026
fbf49c6
fix(dashboard): calldata badge icon color, link affordance, wrapping
pikonha Aug 11, 2026
a0d97f4
test(dashboard): deflake e2e suite and add checkly synthetic monitoring
brunod-e Aug 11, 2026
82c60de
refactor(dashboard): icon + tooltip instead of a calldata review label
pikonha Aug 11, 2026
36b3c92
perf(dashboard): start the calldata review fetch with the proposals q…
pikonha Aug 11, 2026
6bd3050
fix(dashboard): keep the address column readable on whitelabel stakeh…
brunod-e Aug 12, 2026
00bb812
chore(dashboard): drop redundant column width comments
brunod-e Aug 12, 2026
f041e49
fix(ci): unbreak PR checks and align playwright with checkly runtime
brunod-e Aug 12, 2026
acbbe79
fix(ci): pass e2e env through turbo strict mode and widen api hook ti…
brunod-e Aug 12, 2026
ae03c98
chore(dashboard): rename the checkly monitor to anticapture-dashboard
brunod-e Aug 12, 2026
14b5955
test(dashboard): widen smoke coverage and give the CI webServer more …
brunod-e Aug 12, 2026
fea1fea
fix(ci): point dashboard e2e at shared dev services instead of the PR…
brunod-e Aug 12, 2026
09f53a8
fix(ci): source the e2e gateful token from the Vercel project env
brunod-e Aug 12, 2026
905958b
fix(ci): probe gateful token candidates instead of trusting the first…
brunod-e Aug 12, 2026
b76e0d3
fix(ci): log gateful token entry metadata to diagnose the 401s
brunod-e Aug 12, 2026
9c6f3c6
Merge pull request #2116 from blockful/fix/whitelabel-stakeholders-ad…
pikonha Aug 12, 2026
8546082
fix(ci): adopt a coherent gateway plus token pair per Vercel environment
brunod-e Aug 12, 2026
d4eacd6
fix(ci): simplify the e2e token handling to a single fail-fast probe
brunod-e Aug 12, 2026
1462bc3
fix(ci): accept gateful deployments newer than the release commit
pikonha Aug 12, 2026
684f1fe
fix(ci): don't wait out gateful deployments that report no commit
pikonha Aug 12, 2026
da4013e
fix: calldata review label from figma
pikonha Aug 12, 2026
4a24a70
Merge pull request #2111 from blockful/chore/release-wait-gateful
pikonha Aug 12, 2026
8137294
perf(dashboard): cache the calldata review tree independently of the DAO
pikonha Aug 12, 2026
669294b
Merge pull request #2113 from blockful/feat/proposal-calldata-reviewed
pikonha Aug 12, 2026
b74d3e0
chore: version packages
github-actions[bot] Aug 12, 2026
4ec822c
Merge pull request #2117 from blockful/changeset-release/dev
pikonha Aug 12, 2026
a21bef2
fix: match ens proposals by numeric
pikonha Aug 12, 2026
8013fab
Merge pull request #2119 from blockful/fix/ens-calldata-badge
pikonha Aug 12, 2026
e69a484
Merge pull request #2115 from blockful/test/dashboard-synthetic-tests
pikonha Aug 12, 2026
7e46cb2
fix(ci): treat a gateful with no commit as stale while waiting
pikonha Aug 12, 2026
99f4f90
fix(ci): scope dashboard E2E to trusted PRs and codegen to the PR pre…
pikonha Aug 12, 2026
6601ba2
docs(ci): record the known ceilings behind the E2E and release schema…
pikonha Aug 12, 2026
f7943f8
Merge branch 'main' into dev
pikonha Aug 13, 2026
4bf577e
chore: version packages
github-actions[bot] Aug 13, 2026
dbd2f4f
Merge pull request #2120 from blockful/changeset-release/dev
pikonha Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .changeset/walletconnect-absolute-rpc-url.md

This file was deleted.

48 changes: 48 additions & 0 deletions .github/workflows/checkly-deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Checkly deploy

# Keeps the Checkly monitors in sync with the repo. `checkly deploy` is the
# only sanctioned write path for monitor config: edits made in the Checkly UI
# are overwritten by the next run of this workflow.
on:
push:
branches: [main]
paths:
- "apps/dashboard/e2e/**"
- "apps/dashboard/checkly.config.ts"
- "apps/dashboard/playwright.config.ts"
- "apps/dashboard/package.json"
- ".github/workflows/checkly-deploy.yaml"
workflow_dispatch:

jobs:
checkly-deploy:
name: Deploy Checkly monitors
runs-on: ubuntu-latest
timeout-minutes: 10
env:
CHECKLY_API_KEY: ${{ secrets.CHECKLY_API_KEY }}
CHECKLY_ACCOUNT_ID: ${{ secrets.CHECKLY_ACCOUNT_ID }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.10.0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

# Guarded so the workflow is a no-op until the Checkly account secrets
# are configured on the repo.
- name: Deploy checks
if: env.CHECKLY_API_KEY != ''
working-directory: apps/dashboard
run: pnpm exec checkly deploy --force
44 changes: 26 additions & 18 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,30 +43,37 @@ jobs:
run: |
set -euo pipefail
# For each service whose OpenAPI gateful merges into /docs/json: the
# newest commit that touched the paths Railway watches to rebuild it,
# plus every commit after that. First-parent, because that is the
# diff Railway itself sees for a push to this branch.
# commits strictly older than the newest one that touched the paths
# Railway watches to rebuild it — "still the previous release".
# First-parent, because that is the diff Railway itself sees for a
# push to this branch.
#
# A reject list rather than an accept list so that a commit newer
# than this run also passes: Railway stamps a service with the head
# of the push that rebuilt it, and a push that lands while we wait
# would otherwise be unreachable and block until the timeout.
#
# Deliberately not "did THIS push change the service": a push that
# leaves it alone while an earlier push is still deploying it
# (concurrency cancels the earlier waiter) would answer "no" and skip
# the gate while the old spec is still being served. And "or newer"
# rather than an exact commit, because Railway stamps a service with
# the head of the push that rebuilt it — the same commit only when
# that push carried exactly one.
resolve() {
sha=$(git log --first-parent -1 --format=%H -- "$@")
printf '%s' "$(git rev-list --first-parent "${sha}..HEAD" | tr '\n' ',')${sha}"
# leaves it alone while an earlier push is still deploying it would
# answer "no" and skip the gate while the old spec is still served.
#
# ponytail: 200-push window — a service more than 200 pushes behind
# reads as newer and passes. Compare ancestry via the API if that
# ever matters.
older() {
git rev-list --first-parent -n 201 "$1" | tail -n +2 | paste -sd, -
Comment thread
pikonha marked this conversation as resolved.
}
last() { git log --first-parent -1 --format=%H -- "$@"; }
shas=$(jq -cn \
--arg dao "$(resolve apps/api infra/api)" \
--arg relayer "$(resolve apps/relayer infra/relayer)" \
--arg enrichment "$(resolve apps/address-enrichment infra/address-enrichment)" \
--arg dao "$(older "$(last apps/api infra/api)")" \
--arg relayer "$(older "$(last apps/relayer infra/relayer)")" \
--arg enrichment "$(older "$(last apps/address-enrichment infra/address-enrichment)")" \
'{"dao-api": ($dao | split(",")),
"relayer": ($relayer | split(",")),
"address-enrichment": ($enrichment | split(","))}')
echo "shas=${shas}" >> "$GITHUB_OUTPUT"
echo "${shas}" | jq -r 'to_entries[] | "\(.key) must be serving \(.value[-1]) or newer"'
echo "stale=${shas}" >> "$GITHUB_OUTPUT"
echo "stale-gateful=$(older HEAD)" >> "$GITHUB_OUTPUT"
echo "${shas}" | jq -r 'to_entries[] | "\(.key) must be newer than \(.value[0])"'

# `vercel deploy` builds the dashboard, which runs @anticapture/client
# codegen against the live gateful spec. Wait for gateful to serve this
Expand All @@ -77,7 +84,8 @@ jobs:
env:
ANTICAPTURE_API_URL: ${{ secrets.GATEFUL_URL }}
EXPECTED_GATEFUL_SHA: ${{ github.sha }}
EXPECTED_UPSTREAM_SHAS: ${{ steps.upstreams.outputs.shas }}
STALE_UPSTREAM_SHAS: ${{ steps.upstreams.outputs.stale }}
STALE_GATEFUL_SHAS: ${{ steps.upstreams.outputs.stale-gateful }}

- name: Deploy to Vercel
env:
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,55 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# Same resolution as deploy.yaml: gateful merges its upstreams' OpenAPI
# specs into /docs/json, so gateful being on this commit isn't enough —
# a DAO API, the relayer or address enrichment can still serve the old
# spec.
- name: Resolve the commits each upstream should be serving
id: upstreams
run: |
set -euo pipefail
# Reject lists ("still the previous release"), not accept lists: this
# workflow queues rather than cancels, so a push that reaches main
# while we wait can redeploy gateful and its upstreams to a commit
# this run has never seen. Anything newer serves a spec at least as
# new as ours, so accept it instead of timing out on it.
#
# ponytail: 200-push window — a service more than 200 pushes behind
# reads as newer and passes. Compare ancestry via the API if that
# ever matters.
#
# ponytail: accepting a newer gateful also means a release queued
# behind a later push publishes that push's schema under its own
# version. Pinning to this commit instead would just deadlock — the
# spec only exists live, and the superseding push carries no
# changesets to publish in its place. Needs a build-time spec
# artifact per commit to fix properly.
older() {
git rev-list --first-parent -n 201 "$1" | tail -n +2 | paste -sd, -
}
last() { git log --first-parent -1 --format=%H -- "$@"; }
shas=$(jq -cn \
--arg dao "$(older "$(last apps/api infra/api)")" \
--arg relayer "$(older "$(last apps/relayer infra/relayer)")" \
--arg enrichment "$(older "$(last apps/address-enrichment infra/address-enrichment)")" \
'{"dao-api": ($dao | split(",")),
"relayer": ($relayer | split(",")),
"address-enrichment": ($enrichment | split(","))}')
echo "stale=${shas}" >> "$GITHUB_OUTPUT"
echo "stale-gateful=$(older HEAD)" >> "$GITHUB_OUTPUT"
echo "${shas}" | jq -r 'to_entries[] | "\(.key) must be newer than \(.value[0])"'

# The build below runs client codegen against the live gateful spec;
# without this gate it races the redeploy and fails with "fetch failed".
- name: Wait for gateful to be ready
run: node scripts/wait-for-gateful.mjs
env:
ANTICAPTURE_API_URL: ${{ secrets.GATEFUL_URL }}
EXPECTED_GATEFUL_SHA: ${{ github.sha }}
STALE_UPSTREAM_SHAS: ${{ steps.upstreams.outputs.stale }}
STALE_GATEFUL_SHAS: ${{ steps.upstreams.outputs.stale-gateful }}
Comment thread
pikonha marked this conversation as resolved.

- name: Build publishable packages
# @anticapture/client's build depends on codegen, which fetches the live
# Gateful OpenAPI spec and throws without ANTICAPTURE_API_URL (same
Expand Down
182 changes: 113 additions & 69 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,16 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
resolve() {
sha=$(git log --first-parent -1 --format=%H "${HEAD_SHA}" -- "$@")
printf '%s' "$(git rev-list --first-parent "${sha}..${HEAD_SHA}" | tr '\n' ',')${sha}"
# Reject lists ("still the previous release"), see deploy.yaml.
# ponytail: 200-push window, same ceiling as there.
older() {
git rev-list --first-parent -n 201 "$1" | tail -n +2 | paste -sd, -
}
last() { git log --first-parent -1 --format=%H "${HEAD_SHA}" -- "$@"; }
shas=$(jq -cn \
--arg dao "$(resolve apps/api infra/api)" \
--arg relayer "$(resolve apps/relayer infra/relayer)" \
--arg enrichment "$(resolve apps/address-enrichment infra/address-enrichment)" \
--arg dao "$(older "$(last apps/api infra/api)")" \
--arg relayer "$(older "$(last apps/relayer infra/relayer)")" \
--arg enrichment "$(older "$(last apps/address-enrichment infra/address-enrichment)")" \
'{"dao-api": ($dao | split(",")),
"relayer": ($relayer | split(",")),
"address-enrichment": ($enrichment | split(","))}')
Expand All @@ -169,7 +171,7 @@ jobs:
# upstream check rides along with it: the script skips it entirely
# when there is no expected gateful commit.
EXPECTED_GATEFUL_SHA: ${{ needs.configure-vercel-preview.outputs.trusted == 'true' && github.event.pull_request.head.sha || '' }}
EXPECTED_UPSTREAM_SHAS: ${{ needs.configure-vercel-preview.outputs.trusted == 'true' && steps.upstreams.outputs.shas || '' }}
STALE_UPSTREAM_SHAS: ${{ needs.configure-vercel-preview.outputs.trusted == 'true' && steps.upstreams.outputs.shas || '' }}

deploy-vercel-preview:
name: Deploy Vercel preview
Expand Down Expand Up @@ -454,65 +456,107 @@ jobs:
flags: api
fail_ci_if_error: false

# dashboard-e2e:
# name: Dashboard E2E
# needs: wait-for-gateful
# if: ${{ !(startsWith(github.head_ref, 'changeset-release/') && github.actor == 'github-actions[bot]' && github.event.pull_request.user.login == 'github-actions[bot]' && github.event.pull_request.head.repo.full_name == github.repository) }}
# runs-on: ubuntu-latest
# timeout-minutes: 20

# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# with:
# fetch-depth: 0

# - name: Setup pnpm
# uses: pnpm/action-setup@v4
# with:
# version: 10.10.0

# - name: Setup Node.js
# uses: actions/setup-node@v4
# with:
# node-version: "20"
# cache: "pnpm"

# - name: Install dependencies
# run: pnpm install --frozen-lockfile

# - name: Cache Playwright browsers
# uses: actions/cache@v4
# with:
# path: ~/.cache/ms-playwright
# key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
# restore-keys: |
# playwright-${{ runner.os }}-

# - name: Install Playwright browsers
# run: pnpm --filter @anticapture/dashboard exec playwright install --with-deps chromium

# - name: Run dashboard E2E tests
# run: pnpm dashboard test:e2e
# env:
# CI: "true"
# NEXT_PUBLIC_WC_PROJECT_ID: ${{ secrets.E2E_NEXT_PUBLIC_WC_PROJECT_ID }}
# BLOCKFUL_API_TOKEN: ${{ secrets.E2E_BLOCKFUL_API_TOKEN }}
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
# TURBO_TEAM: ${{ secrets.TURBO_TEAM }}

# - name: Upload Playwright report
# if: failure()
# uses: actions/upload-artifact@v4
# with:
# name: playwright-report
# path: apps/dashboard/playwright-report
# retention-days: 14

# - name: Upload Playwright traces
# if: failure()
# uses: actions/upload-artifact@v4
# with:
# name: playwright-test-results
# path: apps/dashboard/test-results
# retention-days: 14
dashboard-e2e:
name: Dashboard E2E
needs: [configure-vercel-preview, wait-for-gateful]
# Trusted authors only: fork PRs get no repository secrets, so
# E2E_BLOCKFUL_API_TOKEN is empty there and every external contribution
# would fail on the probe below without a single test having run.
if: ${{ needs.configure-vercel-preview.outputs.trusted == 'true' && !(startsWith(github.head_ref, 'changeset-release/') && github.actor == 'github-actions[bot]' && github.event.pull_request.user.login == 'github-actions[bot]' && github.event.pull_request.head.repo.full_name == github.repository) }}
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.10.0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
playwright-${{ runner.os }}-

- name: Install Playwright browsers
run: pnpm --filter @anticapture/dashboard exec playwright install --with-deps chromium

# Gateful tokens are revocable server-side, so a stale secret used to
# surface as 9 minutes of data specs dying on 401s. Probe once up front
# and fail in seconds with the actual fix instead. (Reading the token
# from Vercel is not an option: the env API returns ciphertext for
# encrypted values; only deployments receive plaintext.)
- name: Check the gateful token still authenticates
shell: bash
env:
E2E_TOKEN: ${{ secrets.E2E_BLOCKFUL_API_TOKEN }}
Comment thread
pikonha marked this conversation as resolved.
GATEFUL_URL: https://dev-gateful.up.railway.app
run: |
set -euo pipefail
code=$(curl --silent --output /dev/null --write-out "%{http_code}" \
--header "Authorization: Bearer ${E2E_TOKEN}" \
"${GATEFUL_URL}/ens/proposals?limit=1&skip=0" || echo "000")
echo "${GATEFUL_URL}: HTTP ${code}"
if [ "${code}" != "200" ]; then
echo "::error::E2E_BLOCKFUL_API_TOKEN no longer authenticates against ${GATEFUL_URL}. Update it with a valid dev token: gh secret set E2E_BLOCKFUL_API_TOKEN"
exit 1
fi

- name: Run dashboard E2E tests
run: pnpm dashboard test:e2e
env:
CI: "true"
# Shared dev services, NOT the PR's own preview stack: previews run
# their own authful, so the dev-issued e2e token gets 401s there.
ANTICAPTURE_API_URL: https://dev-gateful.up.railway.app
Comment thread
pikonha marked this conversation as resolved.
Comment thread
pikonha marked this conversation as resolved.
# …but `test:e2e` builds the dashboard, which regenerates
# @anticapture/client from the spec. Reading it off dev would omit
# any hook this PR adds and break the build before E2E starts, so
# point codegen (and only codegen) at the PR's preview gateful —
# resolveGatefulOpenApiSpecUrl prefers this over ANTICAPTURE_API_URL.
#
# ponytail: codegen and runtime therefore sit on different contracts
# — a PR that adds an endpoint and immediately exercises it in a spec
# 404s against dev. Closing that needs a preview-issued token (the
# preview authful seeds its own from SEED_TOKEN_PLAINTEXT); wire one
# into a repo secret and point both at the preview stack if it bites.
RAILWAY_ENVIRONMENT_NAME: anticapture-pr-${{ github.event.pull_request.number }}
# The /api/user auth/session proxy throws on boot without this, and
# the resulting same-origin 500s trip the suite's 5xx watcher.
USER_API_URL: https://user-api-dev-9002.up.railway.app
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ${{ secrets.E2E_NEXT_PUBLIC_WC_PROJECT_ID }}
BLOCKFUL_API_TOKEN: ${{ secrets.E2E_BLOCKFUL_API_TOKEN }}
Comment thread
pikonha marked this conversation as resolved.
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}

- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: apps/dashboard/playwright-report
retention-days: 14

- name: Upload Playwright traces
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-test-results
path: apps/dashboard/test-results
retention-days: 14
15 changes: 15 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
{
"mcpServers": {
"anticapture": {
"type": "http",
"url": "https://mcp.anticapture.com/mcp",
"headers": {
"Authorization": "Bearer ${ANTICAPTURE_MCP_TOKEN}"
}
},
"grafana": {
"command": "uvx",
"args": ["mcp-grafana"],
"env": {
"GRAFANA_URL": "https://metrics.anticapture.com",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "${GRAFANA_SERVICE_ACCOUNT_TOKEN}"
}
},
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
Expand Down
Loading
Loading