Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 55 additions & 0 deletions .github/actions/web-checks/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright the Vortex contributors

name: Web checks
description: Format, lint, build without a database, and run the test suite.
runs:
using: composite
steps:
- name: Check infrastructure trust policies offline
shell: bash
run: |
bash -n infra/provision.sh
python3 -m unittest discover -s infra/tests -v
- name: Install pnpm
shell: bash
run: npm install -g pnpm@11.5.2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '24'
cache: pnpm
cache-dependency-path: web/pnpm-lock.yaml
- name: Install dependencies
shell: bash
working-directory: web
run: pnpm install --frozen-lockfile
- name: Format check
shell: bash
working-directory: web
run: pnpm format:check
- name: Lint
shell: bash
working-directory: web
run: pnpm lint
- name: Build without database credentials
shell: bash
working-directory: web
run: pnpm build
- name: Require Docker for the integration suite
shell: bash
run: docker info > /dev/null
- name: Install uv for migration tests
uses: spiraldb/actions/.github/actions/setup-uv@0.18.6
with:
sync: false
- name: Test migration runner
shell: bash
run: uv run --no-project --with 'psycopg[binary]>=3.2' python -m unittest discover -s scripts/tests -v
- name: Test deployment verification
shell: bash
run: node --test scripts/tests/verify-web-deploy.test.mjs
- name: Test
shell: bash
working-directory: web
run: pnpm test
58 changes: 2 additions & 56 deletions .github/workflows/web-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,68 +22,14 @@ concurrency:
group: web-ci-${{ github.ref }}
cancel-in-progress: true

env:
# Keep in lockstep with `packageManager` in web/package.json.
PNPM_VERSION: "11.5.2"

jobs:
web:
name: format, lint, build, test
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: web
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Check infrastructure trust policies offline
working-directory: .
run: |
bash -n infra/provision.sh
python3 -m unittest discover -s infra/tests -v

# pnpm must exist before setup-node so its `cache: pnpm` can resolve the store.
- name: Install pnpm
run: npm install -g "pnpm@${PNPM_VERSION}"

- name: Setup Node
uses: actions/setup-node@v4
with:
# Keep in lockstep with @types/node in web/package.json (Node 24 API surface).
node-version: "24"
cache: pnpm
cache-dependency-path: web/pnpm-lock.yaml

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

- name: Format check
run: pnpm format:check

- name: Lint
run: pnpm lint

# `next build` must succeed without a reachable database (every page and route
# is request-rendered), so no BENCH_DB_* env vars are provided here on purpose.
- name: Build
run: pnpm build

# testcontainers self-skips the Postgres integration suite when no Docker daemon
# is available (a local-dev convenience); in CI that would silently drop the only
# Postgres integration coverage, so fail loudly if the daemon is missing.
- name: Require Docker for the integration suite
run: docker info > /dev/null

- name: Install uv for migration tests
uses: spiraldb/actions/.github/actions/setup-uv@0.18.6
with:
sync: false

- name: Test migration runner
working-directory: .
run: uv run --no-project --with 'psycopg[binary]>=3.2' python -m unittest discover -s scripts/tests -v

- name: Test
run: pnpm test
- name: Run web checks
uses: ./.github/actions/web-checks
71 changes: 57 additions & 14 deletions .github/workflows/web-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ on:
push:
branches: [develop]
pull_request:
branches: [develop]
paths:
- 'web/**'
- 'migrations/**'
- 'scripts/**'
- '.github/workflows/web-deploy.yml'
- '.github/workflows/web-ci.yml'
- '.github/actions/web-checks/**'
workflow_dispatch:
inputs:
environment:
Expand All @@ -35,19 +38,13 @@ on:
default: preview

concurrency:
# Include the event name so a manual workflow_dispatch (e.g. a preview deploy) on
# `develop` does NOT land in the same group as — and thus cancel — an in-flight
# push-triggered production deploy on the same ref.
group: web-deploy-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
# Manual and push production runs share one promotion/verification queue.
# Let the active production run finish; superseded previews can be cancelled.
group: ${{ (github.event_name == 'push' || inputs.environment == 'production') && 'web-deploy-production' || format('web-deploy-preview-{0}-{1}', github.event_name, github.ref) }}
cancel-in-progress: ${{ github.event_name != 'push' && inputs.environment != 'production' }}

permissions:
contents: read
pull-requests: write
# Create the GitHub Deployment + statuses that render the native "View
# deployment" button and the in-progress spinner on the PR (Vercel's git
# integration is off here, so the workflow posts these itself).
deployments: write

env:
VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID }}
Expand All @@ -57,7 +54,27 @@ env:
PNPM_VERSION: "11.5.2"

jobs:
checks:
name: Check deployment commit
runs-on: ubuntu-latest
timeout-minutes: 30
if: >-
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository) &&
(github.event_name != 'workflow_dispatch' || inputs.environment != 'production' ||
github.ref == 'refs/heads/develop')
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
- uses: ./.github/actions/web-checks

deploy:
needs: checks
permissions:
contents: read
pull-requests: write
deployments: write
name: Build & deploy to Vercel
runs-on: ubuntu-latest
timeout-minutes: 20
Expand All @@ -69,8 +86,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.sha }}

# Open a GitHub Deployment against the PR head commit BEFORE the build, so
# Open a GitHub Deployment against the tested merge commit before the build, so
# the PR shows an in-progress (spinner) status immediately and the later
# success/failure step flips it to the "View deployment" button. PR-only;
# `transient_environment` lets GitHub retire superseded previews. A failure
Expand All @@ -81,7 +100,7 @@ jobs:
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_SHA: ${{ github.sha }}
run: |
set -Eeuo pipefail
id="$(gh api "repos/${GITHUB_REPOSITORY}/deployments" --method POST --jq '.id' --input - <<EOF || true
Expand Down Expand Up @@ -142,10 +161,19 @@ jobs:
echo "env=production" >> "$GITHUB_OUTPUT"
fi

- name: Require a public production verification URL
if: steps.target.outputs.env == 'production'
env:
PROD_URL: ${{ vars.BENCH_SITE_BASE_URL }}
run: |
[[ "$PROD_URL" == https://* ]] || { echo "::error::Set BENCH_SITE_BASE_URL to the public HTTPS production URL."; exit 1; }

- name: Pull Vercel project settings
run: vercel pull --yes --environment="${{ steps.target.outputs.env }}" --token="${{ secrets.VERCEL_TOKEN }}"

- name: Build
env:
BENCH_BUILD_SHA: ${{ github.sha }}
run: |
set -Eeuo pipefail
if [ "${{ steps.target.outputs.env }}" = "production" ]; then
Expand All @@ -166,6 +194,21 @@ jobs:
echo "url=${url}" >> "$GITHUB_OUTPUT"
printf 'Deployed: %s\n' "$url"

- name: Verify deployed build and read paths
id: verify
timeout-minutes: 4
env:
DEPLOY_URL: ${{ steps.deploy.outputs.url }}
PROD_URL: ${{ vars.BENCH_SITE_BASE_URL }}
TARGET_ENV: ${{ steps.target.outputs.env }}
EXPECTED_SHA: ${{ github.sha }}
run: |
if [ "$TARGET_ENV" = production ]; then
node scripts/verify-web-deploy.mjs "$PROD_URL" "$EXPECTED_SHA" production
else
node scripts/verify-web-deploy.mjs "$DEPLOY_URL" "$EXPECTED_SHA" preview
fi

# On a pull_request, post the preview URL back to the PR as a single sticky
# comment (edited in place on each push) so the preview is one click from the PR.
- name: Comment preview URL on the PR
Expand Down Expand Up @@ -197,7 +240,7 @@ jobs:
"state": "success",
"environment_url": "${DEPLOY_URL}",
"log_url": "${RUN_URL}",
"description": "Preview ready"
"description": "${{ steps.verify.outputs.result == 'protected' && 'Deployed; verification blocked by protection' || 'Preview verified' }}"
}
EOF

Expand Down
37 changes: 33 additions & 4 deletions docs/architecture/deploy-and-infra.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,20 @@ scripts/migrate-schema.py ──IAM token, verify-full TLS──▶ RDS as `mi

```
push to develop ──▶ production deploy
workflow_dispatch ──▶ preview or production (input)
workflow_dispatch ──▶ preview or production (develop only)
same-repo PR ──▶ preview (event merge commit)
runner: pnpm install + `vercel build [--prod]` (builds web/ ON THE RUNNER)
shared checks on exact SHA: format + lint + build + test
`vercel deploy --prebuilt [--prod]` (uploads the prebuilt output)
runner: `vercel build [--prod]` on the same SHA
`vercel deploy --prebuilt [--prod]`
verify build SHA + read paths + CDN HIT (bounded retries)
```

- The Vercel project is **independently owned** by this repo: keyed by
Expand All @@ -109,10 +116,32 @@ runner: pnpm install + `vercel build [--prod]` (builds web/ ON THE RUNNER)
prebuilt output is then uploaded, eliminating a build race.
- `develop` **is** production — there is no staging gate. The per-PR CI
(including a testcontainer migration test) is the gate; a merge to `develop`
ships straight to the v4 production domain.
ships to the v4 production domain after the deploy workflow checks the same commit.
- Push and manual production runs share one concurrency group without cancelling an active run.
This serializes promotion and verification across both triggers. Previews can cancel stale runs.
- Production verification uses `BENCH_SITE_BASE_URL`. A protected preview reports verification
as blocked. A production authentication error or failed probe fails the workflow.
- A `web-keep-warm` scheduled workflow pings the production deployment so the RDS
connection pool and Data Cache stay warm between visits.

### Required merge check

After the first standalone Web CI run completes, configure the branch protection rule or ruleset
for `develop` to require `format, lint, build, test`, with **GitHub Actions** as its source
(app ID `15368`). Enable **Require branches to be up to date before merging**. These are operator
settings. The workflows do not change repository protection.

From a checkout of that tested commit, verify the context and integration before saving the rule:

```bash
checked_sha="$(git rev-parse HEAD)"
gh api "repos/vortex-data/benchmarks-website/commits/${checked_sha}/check-runs" \
--jq '.check_runs[] | select(.name == "format, lint, build, test") | {name, conclusion, app_id: .app.id}'
```

The completed check must report `success` and app ID `15368`. The shared composite preserves
this standalone check name when its internal steps change.

## Legacy v3 host deploy (`ops/`) — decommissioned 2026-07-08

The v3 Rust server ran on an EC2 host under systemd, deployed by a **polling**
Expand Down
4 changes: 3 additions & 1 deletion docs/runbooks/deploy-secrets-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ variable.
| `VERCEL_TOKEN` (secret) | `web-deploy.yml` |
| `VERCEL_ORG_ID` | `web-deploy.yml` |
| `VERCEL_PROJECT_ID` | `web-deploy.yml` |
| `BENCH_SITE_BASE_URL` | `web-keep-warm.yml` |
| `BENCH_SITE_BASE_URL` | `web-keep-warm.yml`, production verification in `web-deploy.yml` |
| `GH_BENCH_SCHEMA_ROLE_ARN` | `schema-deploy.yml` |
| `RDS_BENCH_REGION` | `schema-deploy.yml` |
| `RDS_BENCH_INSTANCE_ENDPOINT` | `schema-deploy.yml` |
Expand Down Expand Up @@ -349,6 +349,8 @@ export VERCEL_ORG_ID="<org id from Section A>" # the value you set as t
export VERCEL_PROJECT_ID="<project id from Section A>"
read -rs VERCEL_TOKEN && export VERCEL_TOKEN # paste at the prompt; never echoed, never recorded in shell history
# Run from the REPO ROOT (not web/) to match the workflow's project-resolution path:
BENCH_BUILD_SHA="$(git rev-parse HEAD)"
export BENCH_BUILD_SHA
vercel pull --yes --environment=preview --token="$VERCEL_TOKEN" && vercel build --token="$VERCEL_TOKEN"
unset VERCEL_TOKEN # drop the secret from the environment when done
```
Expand Down
Loading
Loading