Operating your deployment
Once your central service and at least one tenant are running, this page covers the day-to-day: optional central features you can turn on, growing your deployment, and keeping it up to date. None of this is required to get running — see Self-hosting and Adding tenants for the must-do path.
Optional: superadmin dashboard
The LegiScan central can serve a superadmin dashboard with its own magic-link login and cross-domain single sign-on. It's optional — central works fine without it. To turn it on, first make sure your central/wrangler.toml has the [env.legiscan.assets] block from the example (it tells Cloudflare to serve the dashboard SPA), then set these secrets on central (run from inside central/):
# Magic-link email for the dashboard
wrangler secret put RESEND_API_KEY --env legiscan
# ES256 private JWK (JSON string); central is the sole issuer
wrangler secret put SUPERADMIN_JWT_PRIVATE_KEY --env legiscan
# Comma-separated list of superadmin email addresses
wrangler secret put SUPERADMIN_EMAILS --env legiscanGenerate an ES256 keypair with any JWK tool. Central holds the private half; each tenant verifies with the matching public JWK, which is not a secret — it goes in each tenant's [env.*.vars] as SUPERADMIN_JWT_PUBLIC_KEY (see the tenant env block in Adding tenants).
Optional: observability panels
These power the operations dashboards and the Members "Login activity" panel. Each one quietly does nothing when its credentials are unset — no crash, the feature just stays dark.
CF_ANALYTICS_TOKEN covers two separate features and needs one permission for each — both can live on the same token:
- D1 anomaly watch — needs the
CF_ACCOUNT_IDvar (set in[env.legiscan.vars], not a secret) plus a token with Account → D1: Read. - Login Activity delivery status — a zone-level lookup, so it needs Zone → Analytics: Read on your app's zone, plus the zone's ID in the
CF_FLOORVOTE_ZONE_IDvar (also in[env.legiscan.vars]— this is the zone ID from your domain's Overview page in the Cloudflare dashboard, not the account ID). Skip this if you don't need delivery-status detail; the token still works for D1 anomaly watch with just the account permission.
# Cloudflare API token: Account "D1: Read" + Zone "Analytics: Read"
wrangler secret put CF_ANALYTICS_TOKEN --env legiscan
# Cloudflare API token, scoped: Email Sending: Read
wrangler secret put CF_EMAIL_TOKEN --env legiscanAdding a new state to an existing tenant
Update state_coverage in the tenant's association_config, then load that state's bills into central (see the seeding steps in Adding tenants). Central will then notify the tenant of any bills matching its keywords.
Adding another tenant
Each new organization or topic focus is another tenant. Follow Adding tenants again — you reuse the same account-level Cloudflare credentials every time, so the per-tenant work is small.
Deploying with GitHub Actions (optional)
The repo ships a manual deploy workflow at .github/workflows/deploy.yml (workflow_dispatch — it never fires on push). It wraps the same npm run deploy:* scripts this guide uses locally, so deploys run in CI instead of from your machine: auditable, and independent of any one laptop. Local wrangler still works and stays the fallback.
One-time setup:
- Create a Cloudflare API token scoped to your account with Workers Scripts: Edit, D1: Edit, and Queues: Edit (add Workers Routes: Edit only if your deploys change routes). Add it as the repository secret
CLOUDFLARE_API_TOKEN(repo → Settings → Secrets and variables → Actions). - Create a GitHub Environment named
production(repo → Settings → Environments) and scope the token to it. Restrict its deployment branches to your default branch; on GitHub Team or Pro you can also require reviewer approval.
Deploying:
# From your default branch — deploy one tenant, or central:
gh workflow run deploy.yml --ref main -f target=tenant -f tenant=<slug>
gh workflow run deploy.yml --ref main -f target=central
gh run watchTriggering with gh (or the Actions tab → "Deploy (manual)" → Run workflow) starts the run with your GitHub token; the Cloudflare token stays inside the Actions runner and never touches your laptop.
Upgrading
Pull the latest code and redeploy. Always use the deploy scripts so migrations and the frontend build run:
git pull origin main
cd central && npm install && npm run deploy:legiscan
# Repeat for each tenant
cd ../api && npm run deploy:tenant -- org-njMonitoring
Each tenant Worker exposes GET /api/health. On the LegiScan central, the health check is also GET /api/health (the bare /health path is served by the admin dashboard). The central worker pulls aggregate engagement stats from each tenant once a day, at 06:00 UTC.