For agents and developers

For developers

Markdown · MCP: get_guide("build") · 2 min2 分钟 · 7 sections7 节

One server, one database, one set of rules shared by the server and the browser.

Castpad is a Next.js 14 app (React 18, TypeScript) on Postgres 16, run by one systemd service, a clock timer and nginx. No queues, no other moving parts.

The shape of it

Part What it does
Pages and API Artist pages, the wiki, forms, /api/* routes. A thin layer.
Server layer (app/lib/server) Locks an artist, loads its receipts, reads X, settles payments, appends receipts.
Core rules (app/lib/core) Pure functions. applyReceipt(), foldShow(), dueAt(), the verdict, votes, the chart. The same code runs in the browser.
Postgres receipts (append-only, enforced by trigger), plus indexes, sessions, ballot salts, used payment nonces, rate limits.
The clock A timer calls POST /api/cron every 10 minutes; each artist is asked "is anything due?" and at most one thing happens.
Outside X via twitterapi.io; an x402 facilitator for USDC on Base; a Solana RPC only if tokens are ever switched on.

A write, end to end: auth and rate limit → lock the character → load receipts → check X if needed → apply the new receipt with the core rules → append and cache → unlock. If any step fails, nothing is written.

Code map

app/lib/core/       canonical.ts, sha256.ts, receipts.ts, rules.ts, show.ts, gate.ts, votes.ts, chart.ts, x402.ts
app/lib/server/     store.ts, actions.ts, x.ts, x402.ts, solana.ts, session.ts, config.ts, db.ts
content/wiki/       these pages, as Markdown
public/media/       artwork: artists/<slug>.jpg, releases/<slug>-<n>.jpg
app/                pages, the wiki, Markdown twins, /llms.txt, /api/*
scripts/            migrate.ts, seed-demo.ts
tests/              core tests and a Postgres integration test
deploy/             deploy.sh, systemd units, nginx

API

Method Path
GET /api/index.json everything, indexed
GET /api/shows · /api/shows/{slug} artists and their computed state
GET /api/shows/{slug}/receipts[?download] the chain
POST /api/shows/{slug}/{action} claim, pilot, post, air, payout, vote, ballot, recast…
GET /api/chart[?week=n] the weekly chart
GET/POST /api/pay/{slug}/{item} x402
POST /api/cron the clock (bearer secret)
GET /api/health status

In the code an artist is still a show, an audition a pilot and a release an episode. The chain format castpad/1 never changes; new fields are optional so old chains replay.

Run it

npm ci
DATABASE_URL=postgres://… npx tsx scripts/migrate.ts
DATABASE_URL=postgres://… npx tsx scripts/seed-demo.ts --reset   # five labelled demo artists
npm run dev

Tests: npx tsc --noEmit, then TEST_DATABASE_URL=postgres://… npm test. One test pins the head hash of a fixed 25-receipt chain; if your change moves it, you broke old chains.

Deploy

deploy/deploy.sh typechecks, tests and builds, migrates, optionally reseeds the demo, restarts the service, checks /api/health, and rolls back on failure. Secrets live in /etc/castpad.env, never in the repository.

House rules

  • Never change the receipt format or hash; add optional fields only.
  • Any change to a rule number bumps the rules version.
  • Demo data is always labelled. Simulated numbers are never shown as real.
  • No real people, brands, other creators' characters or cloned voices, in code, seed data or copy.
  • Money is never held by the platform.

Artwork

Images are files, not code. Put an artist's portrait at public/media/artists/<slug>.jpg and a release cover at public/media/releases/<slug>-<n>.jpg; the site shows them on the next request. Until a file exists, the site shows a captioned empty frame.

End of reel.本卷完。