Skip to content

PayPal sandbox only. No real money moves.

OperatorsRun it yourself

Operator runbook

Deploy, set secrets by name, check the result, and what to do when something goes wrong. Names only, never values.

Unverifieddescribed, but not yet checked

Page last changed 7 Nov 2026 (from the git history)

This page is for the person who runs the server. It is built from docs/DEPLOY.md, docs/RUNBOOK-GATES.md, docs/RUNBOOK-BACKUP.md and infra/. Everything points at the PayPal sandbox.

Roles

RoleCan do
OwnerChange repository secrets and the sandbox-nightly, staging and production environments, use the PayPal developer dashboard, run anything against the real sandbox from a laptop
ContributorRead evidence and workflow logs, run the fake PayPal and the text-level voice eval, open pull requests. Never receives secrets

Deploy

Apply the blueprint

In Render choose New, Blueprint, pick the repository and apply render.yaml. It describes one web service, gavel-host, with one instance and a 1 GB disk at /var/data. Health check path /healthz. Auto deploy is off. The plan must allow a persistent disk.

Fill the variables the blueprint leaves empty

Names only, set in the service Environment tab (see the table below).

Wire GitHub to Render

Copy the Render deploy hook. In GitHub, environment staging: secret RENDER_DEPLOY_HOOK_URL, optional secret ADMIN_KEY (same value as on Render, enables the create-auction smoke check), variable STAGING_URL.

Register the webhook

In the PayPal developer dashboard (sandbox app, Webhooks) add https://<api-host>/webhooks/paypal for PAYMENT.AUTHORIZATION.VOIDED, PAYMENT.AUTHORIZATION.DENIED, PAYMENT.CAPTURE.PENDING, PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED and PAYMENT.CAPTURE.DECLINED. Other types are acknowledged and ignored. Copy the webhook id into PAYPAL_WEBHOOK_ID on Render only.

Run the deploy workflow and read the smoke output

Run deploy-staging by hand. It calls the deploy hook, waits 60 seconds, then runs scripts/smoke.sh.

Deploy the web app

Create a Cloudflare Pages project, build with npm ci && npm run build -w @gavel/web and pass VITE_API_BASE and VITE_WS_BASE. Add the Pages origin to CORS_ORIGINS.

Production uses the deploy-prod workflow: tag the commit or run it by hand, approval on the production environment (needs a required reviewer), deploy only if the commit is the head of main, poll GET /version until it serves the commit, then run the smoke test. If the commit never appears, the previous deploy is restored. Details: infra/rollback.md.

Secrets and variables, by name

Prop

Type

The full list the server reads is .env.example in the repository root. Never paste a value into an issue, a log or a pull request.

Post-deploy checks

CheckExpected
GET /healthz200 while the process runs
GET /readyz200 when migrations are applied and the store answers, else 503
GET /warmup200 when the warm steps finish within 3000 ms, else 503 with a per-step report
GET /version{"sha":"<commit>"} of the deployed commit
unsigned POST /webhooks/paypal400
POST /api/auctions without a key401 (201 with the admin key)

By hand: ADMIN_KEY=... scripts/smoke.sh https://<api-host>. The smoke can pass against the old instance if it answers before the new build is live.

The uptime workflow probes the three health routes every 5 minutes. It stays off until the variables UPTIME_ENABLED (true) and PRODUCTION_URL are set. Two failed rounds in a row raise an alert.

What to do when

Edit on GitHub

Last updated on