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 9 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 .

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 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 id into PAYPAL_WEBHOOK_ID on Render only.

Run the deploy workflow and read the smoke output

Deploy main with Manual Deploy in the Render dashboard, then run scripts/smoke.sh <api-url>. The optional deploy-staging workflow does the same through the deploy hook once its secrets exist; until then it only reports SKIPPED.

Deploy the web app

The web app is a Cloudflare Worker with static assets (infra/web/wrangler.toml). Build with npm ci && npm run build -w @gavel/web, passing VITE_API_BASE and VITE_WS_BASE, then run npx wrangler deploy from infra/web (the deploy-web workflow does this on pushes to main). Add the Worker 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 health routes plus the web and docs hosts on GitHub's best-effort schedule: it asks for every 15 minutes and has been observed running every 4 to 8 hours. It is on by default (UPTIME_ENABLED set to false turns it off). A failed round is re-probed after 30 seconds, and a failure that stands opens the alert issue at once. For a minute-level probe use an external monitor (see infra/uptime.md); none is configured yet.

What to do when

Edit on GitHub

Last updated on