One tag ships two things: the static binary (a GitHub release) and the
Cloudflare Worker (deploy.yml). The wrangler steps live in
ventures/supportgenius/README.md;
this file is the CI/release split around them.
# `version` lives in the workspace Cargo.toml; it is 0.1.0, so v0.1.0 needs no bump.
git switch main && git pull # main must be green in CI first
git tag -a v0.1.0 -m "SupportGenius v0.1.0"
git push origin v0.1.0.github/workflows/release.yml then builds x86_64-unknown-linux-musl on
ubuntu-latest and aarch64-unknown-linux-musl on ubuntu-24.04-arm, asserts
each binary is statically linked, runs scripts/smoke.sh on each, packages
supportgenius-<target>.tar.gz plus a .sha256 (produced by sha256sum in the
workflow dir), and a single publish job attaches all four files to the GitHub
release.
Verify the release:
gh release view v0.1.0 # lists four assets
BASE=https://github.com/SupportGenius/core/releases/download/v0.1.0
TARGET=x86_64-unknown-linux-musl
curl -fLO $BASE/supportgenius-$TARGET.tar.gz{,.sha256}
sha256sum -c supportgenius-$TARGET.tar.gz.sha256
tar xzf supportgenius-$TARGET.tar.gz && scripts/smoke.sh ./supportgenius.github/workflows/deploy.yml (both jobs pin cloudflare/wrangler-action@v3
to wranglerVersion: "4", so the action's stale default wrangler is never
used):
- push to
main→ staging: thestagingGitHub environment deploys Workersupportgenius-stagingathttps://api-staging.supportgeni.us(wrangler.toml[env.staging],wrangler deploy --env staging); v*tag → production: theproductionGitHub environment (required reviewers, so a human gates it) deploys the top-level config athttps://api.supportgeni.us;- before each deploy it applies that environment's D1 migrations
(
wrangler d1 migrations apply DB --remote [--env staging]); - then it smokes the deployed Worker, curling
/__healthwith--retry 20 --retry-delay 6 --retry-all-errors(a custom domain takes a moment to attach). Staging requires only that/__healthanswers and/__readyis.ok == true. Production additionally requires/__ready.captcha == "configured"and.rate_limiter == "configured", and/__health.mailer == "configured". Those three/__readyfields are added by the Worker itself (ventures/supportgenius/src/lib.rsdecorates the harness's DB-only{"ok":true}probe withtext_model,captchaandrate_limiter, each"configured" | "missing");/__health'smailer/captchaare the harness's own port fields. Note the venture always mounts a mailer (a keyless Resend adapter still reports its port configured), so themailercheck proves the port is mounted, not thatRESEND_API_KEY(or anOWLPOST_API_KEYthat takes precedence over it) is set — the one real join below is what proves that; - it skips with a
::notice::when theCLOUDFLARE_API_TOKENsecret is unset (preflightemitsconfigured=falseand both deploy jobs skip, somainstays green), and fails thepreflightstep — naming the environment — if that environment'sdatabase_idinwrangler.tomlis still aREPLACE_ME…placeholder (production's is top-level, staging's is under[env.staging]).
Repo secrets: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID. The token
needs, at minimum: Account → Workers Scripts: Edit, Account → D1: Edit,
Account → Account Settings: Read, Zone → Workers Routes: Edit (custom
domains), and — for a user-owned token, which wrangler requires — User →
User Details: Read and Memberships: Read. An account-owned token cannot
carry Memberships and wrangler will fail; the stock "Edit Cloudflare Workers"
template lacks D1:Edit, so add it.
bunx wrangler d1 create supportgenius
bunx wrangler d1 create supportgenius-staging
# paste each returned database_id into wrangler.toml ([d1_databases] and [env.staging])
bunx wrangler r2 bucket create supportgenius-uploads
bunx wrangler secret put HARNESS_SECRET # required, >= 32 bytes
bunx wrangler secret put RESEND_API_KEY # required, unless OWLPOST_API_KEY is set
bunx wrangler secret put OWLPOST_API_KEY # optional; takes precedence over RESEND_API_KEY
bunx wrangler secret put TURNSTILE_SECRET # required for production traffic
bunx wrangler secret put ADMIN_TOKEN # optional (admin CSV export)
bunx wrangler secret put ANTHROPIC_API_KEY # optional (grounded answers)
# repeat each with `--env staging` for the staging Worker
bunx wrangler d1 migrations apply DB --remote
bunx wrangler deploy # or push the tag and let deploy.yml deployBefore setting OWLPOST_API_KEY, verify the sending domain
(send.supportgeni.us) in Owlpost; keep RESEND_API_KEY until a real join
confirms Owlpost delivers, then remove it.
Confirm the RATE_LIMITER/VISITOR_RATE_LIMITER namespace_ids are unique in the account (1001/1002 production, 1003/1004 staging), then smoke and do one real join before opening the form:
curl -fsS https://api.supportgeni.us/__health && curl -fsS https://api.supportgeni.us/__ready
curl -fsS -X POST https://api.supportgeni.us/v1/waitlist \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","product":"supportgenius","captchaToken":"<turnstile token>"}'When the confirmation mail arrives, flip data-open="true" on the site's form (the venture README's "The site's half").
For each capability the release ships, update docs/CLAIMS.md and
the website's COPY.md Facts, and flip that capability's chip from
planned. CLAIMS.md is the source of truth that keeps the two in step.