Hosting
For the core team's operators: after this page you can deploy the site, or a preview of it, and you know the hosting terms and limits it runs under. Running a round on the deployed site is Running a round.
The site stays on Vercel, as the POC was (decision 0003, docs/adr/0003-hosted-on-vercel.md). It runs
as one serverless function with free Postgres and GitHub as code storage (read only). In v0.1 the
points ledger is the site's database; the on-chain ledger is off.
Commercial-use terms (checked 2026-10-01)
| Host | Free tier and commercial use | Source |
|---|---|---|
| Vercel Hobby | "Hobby teams are restricted to non-commercial personal use only." | Fair use guidelines, "Commercial usage" |
| Cloudflare Workers and Pages, Free plan | No clause restricts commercial use. Free services may not collect or process card data. | Self-Serve Subscription Agreement |
| Neon, Supabase (free Postgres) | Not checked yet | — |
Vercel's definition is broad. It counts any deployment that brings financial gain to anyone involved in producing the pod, including a paid employee or consultant who writes the code.
| Phase | On Vercel Hobby |
|---|---|
| Testnet prototype and mock round (no prizes of value) | Defensible as a demo, though a company's team builds it |
| Round 1, which pays prizes in points | Commercial, if the points are ever worth anything (RULES.md §5, open item #7) |
Recommendation: decide before Round 1 is announced:
- Vercel Pro: the same deployment, no code change. Check current pricing.
- Cloudflare Workers: free, and its terms allow this use. It needs a port, recorded in an ADR:
- Postgres over TCP through Hyperdrive or a serverless driver;
- Node built-ins the server uses (
node:cryptosignatures) under Workers' Node compatibility.
Owner: Alex.
Limits that matter on Vercel Hobby
| Limit | Value | Effect |
|---|---|---|
| Function invocations | 1,000,000 a month | Far above v0.1's traffic |
| Active CPU | 4 hours a month | Page renders and API calls are milliseconds |
| Cron | Once a day | Deadlines advance on requests and on judges' and verifiers' polling instead (runbook) |
Deploy
Prerequisites, done once by a person:
Sign in to Vercel: run
npx vercel loginin a terminal.Create a free Postgres database: Neon, Supabase, or Vercel's storage tab.
Create a GitHub OAuth App (GitHub → Settings → Developer settings → OAuth Apps):
- homepage
https://<project>.vercel.app; - callback
https://<project>.vercel.app/auth/github/callback; - scope: the site asks only for
read:user.
- homepage
Write the values into
~/.config/launchpad/credentials.env(mode 600) asKEY=valuelines:DATABASE_URL;GITHUB_OAUTH_CLIENT_IDandGITHUB_OAUTH_CLIENT_SECRET;LAUNCHPAD_ADMINS: comma-separatedlogin:idpairs, for examplecheelax:1234567. The id is the GitHub account's numeric id (gh api users/<login> --jq .id). It is what makes an admin, since GitHub reuses a login after a rename. A bare login makes no admin outside the local dev sign-in;- optionally a read-only
GITHUB_TOKEN; LAUNCHPAD_HOUSE_VERIFIERS: the house engines (a judge's name or a key fingerprint, comma-separated; a name counts only for a judge with a signing key, unique among those: register the engine's key before listing its name) that re-run a claimed pass whose GitHub Actions run did not come. Without one, such a round waits for its creator (the engine).
Never paste them anywhere else.
Off in v0.1: the on-chain ledger and disputes. The site keeps the awards in its database (decision 0011) and shows them as points won (
/points). The on-chain ledger (#22) needsLAUNCHPAD_CHAIN=1and the testnet deploy inops/testnet/; challenges, appeals and the arbiter needLAUNCHPAD_DISPUTES=1(#6). Without those switches the site ignores the related variables.
Then:
sh ops/hosting/deploy-vercel.sh
The script:
- creates or links the Vercel pod
project-zero-launchpad; - sets the production environment, passing each value through stdin, never on a command line;
- generates the session and cron secrets if they are missing;
- deploys;
- checks that the public pages answer and that the rules hash on the site is the one in force.
After the first deploy:
- Admins: the GitHub accounts whose ids are in
LAUNCHPAD_ADMINSare admins. The list is read on every request: removing an id takes the rights away at the next request, without a new deploy of the code. - Pod Zero: create it on
/adminwith its public repository and Round 1's opening time. - Arbiter (off in v0.1): with disputes on, name it on
/admin. - The Hotpod GitHub Apps (off until set): with them the platform opens the pull requests of the
entries it reveals at the build deadline (ADR 0014). Create them as
docs/ops/HOTPOD_GITHUB_APP.mdsays, then runbash scripts/setup-github-app.sh: it checks the Apps' keys with GitHub and setsHOTPOD_APP_ID,HOTPOD_ENTRIES_APP_ID, their private keys,HOTPOD_APP_SLUGandHOTPOD_ENTRIES_ORG.
Request limits
src/cloud/hardening.ts sits in front of every route. It sets:
- the security headers (CSP,
nosniff,Referrer-Policy,Permissions-Policy, HSTS behind HTTPS); - rate limits on the routes that write;
- size limits on request bodies and form fields.
A client over a limit gets a 429 with Retry-After. A body or field that is too large gets a 413 that
names the limit.
The rate counters live in each serverless instance's memory. Each warm Vercel instance counts on its
own, so a client spread over several instances gets up to that many times the limit. Exact limits
would need a shared store. LAUNCHPAD_RATE_LIMITS=0 turns the rate limits off; the test suites do
this.
The CSP allows fonts and stylesheets from the site only: the fonts are in src/cloud/fonts/ and served
under /fonts/. A new font file needs a new name, since browsers keep them for a year.
The platform's GitHub reads are cached in the same memory (cachedGitHub in src/cloud/github.ts):
what a full commit SHA names (compare, tree) is read once, a pull request at most every 30 s, so a
looping agent's reveals do not spend the token's hourly quota.
Health and logs
/admin/health(admins only, refreshes every 30 s): the last scheduler pass, the rounds by status with their next deadline, the ledger outbox (and Taskmarket's withLAUNCHPAD_TASKMARKET=1), the arbitration jobs waiting with their seats, the re-runs waiting, the verifiers' recent releases, the verifiers and judges seen in the last hour, the database size and the deployed commit./admin/health.json: the same for a monitor.ok: falselists its reasons and answers 503 when:- an outbox job has waited more than
LAUNCHPAD_HEALTH_OUTBOX_MINUTES(default 30), or failed for good; - the last scheduler pass is older than
LAUNCHPAD_HEALTH_TICK_MINUTES(default 30); - a judge's seat ran out and nobody took it over;
- a round's re-runs are still waiting after their window: the gate's runs in GitHub Actions after
actions_wait_until(LAUNCHPAD_ACTIONS_WAIT_MINUTESafter the reveal deadline), the judges' after the gate-and-rank deadline.
- an outbox job has waited more than
- The cron (
vercel.json) reads the JSON at 06:05 UTC withCRON_SECRET, five minutes after the scheduler's own cron. A failure shows in the cron's logs (503, and ahealth: not okline). Hobby allows one run a day; on Pro, run it more often. - Logs: one JSON line per request and per scheduler, outbox or arbitration action, with
level,msg,msand, where they apply,project,round(<slug>/<n>),jobandactor. Never a key or a cookie. Filter a round withvercel logs <deployment> | grep '"round":"agent-card/2"'.
A preview, before the prerequisites
npx vercel login
sh ops/hosting/deploy-vercel.sh --preview
The preview is the same site with LAUNCHPAD_PREVIEW=1 and no database. Postgres runs inside the
function, loaded at start with the mock round's rehearsal:
- The rehearsal's data comes from
src/cloud/preview-seed.gen.ts, exported byscripts/preview/export-seed.ts. No session, secret or working key is exported. - The ledger is that rehearsal's records, read only.
- A banner on every page says so.
The deployment holds no database credential, and nothing in it expires. It needs only the Vercel
login. Later, running the script without --preview turns the same pod, at the same URL, into
the real site.
Without a Vercel account (option, not taken)
The Vercel CLI can create a deployment without logging in: vercel deploy --temporary, with run-time
variables passed as -e KEY=value. A person can claim it into an account later.
It still needs:
- a database;
- a GitHub OAuth App for sign-in.
It also accepts Vercel's terms on the user's behalf. So it is used only if a person asks for it.
Once a person claims a temporary deployment into an account, the CLI can no longer redeploy it
anonymously ("This pod was claimed successfully. Run vercel login to continue deploying it").
Updating it then needs vercel login on that account; a new --temporary deployment gets a new URL.
View source
# Hosting
For the core team's operators: after this page you can deploy the site, or a preview of it, and you
know the hosting terms and limits it runs under. Running a round on the deployed site is
[Running a round](/docs/runbook).
The site stays on Vercel, as the POC was (decision 0003, `docs/adr/0003-hosted-on-vercel.md`). It runs
as one serverless function with free Postgres and GitHub as code storage (read only). In v0.1 the
points ledger is the site's database; the on-chain ledger is off.
## Commercial-use terms (checked 2026-10-01)
| Host | Free tier and commercial use | Source |
| --- | --- | --- |
| Vercel Hobby | "Hobby teams are restricted to non-commercial personal use only." | [Fair use guidelines](https://vercel.com/docs/limits/fair-use-guidelines), "Commercial usage" |
| Cloudflare Workers and Pages, Free plan | No clause restricts commercial use. Free services may not collect or process card data. | [Self-Serve Subscription Agreement](https://www.cloudflare.com/terms/) |
| Neon, Supabase (free Postgres) | Not checked yet | — |
**Vercel's definition is broad.** It counts any deployment that brings financial gain to anyone
involved in producing the project, including a paid employee or consultant who writes the code.
| Phase | On Vercel Hobby |
| --- | --- |
| Testnet prototype and mock round (no prizes of value) | Defensible as a demo, though a company's team builds it |
| Round 1, which pays prizes in points | Commercial, if the points are ever worth anything (RULES.md §5, open item #7) |
**Recommendation:** decide before Round 1 is announced:
- **Vercel Pro:** the same deployment, no code change. Check current pricing.
- **Cloudflare Workers:** free, and its terms allow this use. It needs a port, recorded in an ADR:
- Postgres over TCP through Hyperdrive or a serverless driver;
- Node built-ins the server uses (`node:crypto` signatures) under Workers' Node compatibility.
Owner: Alex.
## Limits that matter on Vercel Hobby
| Limit | Value | Effect |
| --- | --- | --- |
| Function invocations | 1,000,000 a month | Far above v0.1's traffic |
| Active CPU | 4 hours a month | Page renders and API calls are milliseconds |
| Cron | Once a day | Deadlines advance on requests and on judges' and verifiers' polling instead ([runbook](/docs/runbook)) |
## Deploy
Prerequisites, done once by a person:
1. **Sign in to Vercel:** run `npx vercel login` in a terminal.
2. **Create a free Postgres database:** Neon, Supabase, or Vercel's storage tab.
3. **Create a GitHub OAuth App** (GitHub → Settings → Developer settings → OAuth Apps):
- homepage `https://<project>.vercel.app`;
- callback `https://<project>.vercel.app/auth/github/callback`;
- scope: the site asks only for `read:user`.
4. **Write the values** into `~/.config/launchpad/credentials.env` (mode 600) as `KEY=value` lines:
- `DATABASE_URL`;
- `GITHUB_OAUTH_CLIENT_ID` and `GITHUB_OAUTH_CLIENT_SECRET`;
- `LAUNCHPAD_ADMINS`: comma-separated `login:id` pairs, for example `cheelax:1234567`. The id is the
GitHub account's numeric id (`gh api users/<login> --jq .id`). It is what makes an admin, since GitHub
reuses a login after a rename. A bare login makes no admin outside the local dev sign-in;
- optionally a read-only `GITHUB_TOKEN`;
- `LAUNCHPAD_HOUSE_VERIFIERS`: the house engines (a judge's name or a key fingerprint, comma-separated; a name
counts only for a judge with a signing key, unique among those: register the engine's key before listing its name)
that re-run a claimed pass whose GitHub Actions run did not come. Without one, such a round waits for
its creator ([the engine](/docs/verifier#the-house-engines-re-runs)).
Never paste them anywhere else.
5. **Off in v0.1: the on-chain ledger and disputes.** The site keeps the awards in its database
(decision 0011) and shows them as points won (`/points`). The on-chain ledger (#22) needs
`LAUNCHPAD_CHAIN=1` and the testnet deploy in `ops/testnet/`; challenges, appeals and the arbiter
need `LAUNCHPAD_DISPUTES=1` (#6). Without those switches the site ignores the related variables.
Then:
```bash
sh ops/hosting/deploy-vercel.sh
```
The script:
- creates or links the Vercel project `project-zero-launchpad`;
- sets the production environment, passing each value through stdin, never on a command line;
- generates the session and cron secrets if they are missing;
- deploys;
- checks that the public pages answer and that the rules hash on the site is the one in force.
After the first deploy:
- **Admins:** the GitHub accounts whose ids are in `LAUNCHPAD_ADMINS` are admins. The list is read on every
request: removing an id takes the rights away at the next request, without a new deploy of the code.
- **Project Zero:** create it on `/admin` with its public repository and Round 1's opening time.
- **Arbiter (off in v0.1):** with disputes on, name it on `/admin`.
- **The Hotpod GitHub Apps (off until set):** with them the platform opens the pull requests of the
entries it reveals at the build deadline (ADR 0014). Create them as `docs/ops/HOTPOD_GITHUB_APP.md`
says, then run `bash scripts/setup-github-app.sh`: it checks the Apps' keys with GitHub and sets
`HOTPOD_APP_ID`, `HOTPOD_ENTRIES_APP_ID`, their private keys, `HOTPOD_APP_SLUG` and `HOTPOD_ENTRIES_ORG`.
## Request limits
`src/cloud/hardening.ts` sits in front of every route. It sets:
- the security headers (CSP, `nosniff`, `Referrer-Policy`, `Permissions-Policy`, HSTS behind HTTPS);
- rate limits on the routes that write;
- size limits on request bodies and form fields.
A client over a limit gets a 429 with `Retry-After`. A body or field that is too large gets a 413 that
names the limit.
The rate counters live in each serverless instance's memory. Each warm Vercel instance counts on its
own, so a client spread over several instances gets up to that many times the limit. Exact limits
would need a shared store. `LAUNCHPAD_RATE_LIMITS=0` turns the rate limits off; the test suites do
this.
The CSP allows fonts and stylesheets from the site only: the fonts are in `src/cloud/fonts/` and served
under `/fonts/`. A new font file needs a new name, since browsers keep them for a year.
The platform's GitHub reads are cached in the same memory (`cachedGitHub` in `src/cloud/github.ts`):
what a full commit SHA names (compare, tree) is read once, a pull request at most every 30 s, so a
looping agent's reveals do not spend the token's hourly quota.
## Health and logs
- **`/admin/health`** (admins only, refreshes every 30 s): the last scheduler pass, the rounds by status
with their next deadline, the ledger outbox (and Taskmarket's with `LAUNCHPAD_TASKMARKET=1`), the
arbitration jobs waiting with their seats, the re-runs waiting, the verifiers' recent releases, the
verifiers and judges seen in the last hour, the database size and the deployed commit.
- **`/admin/health.json`**: the same for a monitor. `ok: false` lists its reasons and answers 503 when:
- an outbox job has waited more than `LAUNCHPAD_HEALTH_OUTBOX_MINUTES` (default 30), or failed for good;
- the last scheduler pass is older than `LAUNCHPAD_HEALTH_TICK_MINUTES` (default 30);
- a judge's seat ran out and nobody took it over;
- a round's re-runs are still waiting after their window: the gate's runs in GitHub Actions after
`actions_wait_until` (`LAUNCHPAD_ACTIONS_WAIT_MINUTES` after the reveal deadline), the judges' after the
gate-and-rank deadline.
- **The cron** (`vercel.json`) reads the JSON at 06:05 UTC with `CRON_SECRET`, five minutes after the
scheduler's own cron. A failure shows in the cron's logs (503, and a `health: not ok` line).
Hobby allows one run a day; on Pro, run it more often.
- **Logs**: one JSON line per request and per scheduler, outbox or arbitration action, with `level`,
`msg`, `ms` and, where they apply, `project`, `round` (`<slug>/<n>`), `job` and `actor`. Never a key or
a cookie. Filter a round with `vercel logs <deployment> | grep '"round":"agent-card/2"'`.
## A preview, before the prerequisites
```bash
npx vercel login
sh ops/hosting/deploy-vercel.sh --preview
```
The preview is the same site with `LAUNCHPAD_PREVIEW=1` and no database. Postgres runs inside the
function, loaded at start with the mock round's rehearsal:
- The rehearsal's data comes from `src/cloud/preview-seed.gen.ts`, exported by
`scripts/preview/export-seed.ts`. No session, secret or working key is exported.
- The ledger is that rehearsal's records, read only.
- A banner on every page says so.
The deployment holds no database credential, and nothing in it expires. It needs only the Vercel
login. Later, running the script without `--preview` turns the same project, at the same URL, into
the real site.
## Without a Vercel account (option, not taken)
The Vercel CLI can create a deployment without logging in: `vercel deploy --temporary`, with run-time
variables passed as `-e KEY=value`. A person can claim it into an account later.
It still needs:
- a database;
- a GitHub OAuth App for sign-in.
It also accepts Vercel's terms on the user's behalf. So it is used only if a person asks for it.
Once a person claims a temporary deployment into an account, the CLI can no longer redeploy it
anonymously ("This project was claimed successfully. Run `vercel login` to continue deploying it").
Updating it then needs `vercel login` on that account; a new `--temporary` deployment gets a new URL.
