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.