Skip to content

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/):

bash
# 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 legiscan

Generate 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_ID var (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_ID var (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.
bash
# 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 legiscan

Adding 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:

  1. 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).
  2. 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:

bash
# 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 watch

Triggering 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:

bash
git pull origin main
cd central && npm install && npm run deploy:legiscan

# Repeat for each tenant
cd ../api && npm run deploy:tenant -- org-nj

Monitoring

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.

Development supported by the Bipartisan Policy Center.Architecture and security have been reviewed and strengthened through a volunteer engagement with U.S. Digital Response.Bill data comes from LegiScan, licensed CC BY 4.0; summaries and relevance scores are generated by FloorVote. FloorVote reads legislative data through a provider interface, and LegiScan is the maintained implementation.FloorVote is open source under AGPL-3.0.