Skip to content
CannObservPublic

About

Washington State law, regulation, and policy tracking service

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2,000 Commits

Folders and files

Repository files navigation

usa-wa

Washington State law, regulation, and policy tracking service.

Setup

# Install Python dependencies (creates .venv, locks deps in uv.lock)
uv sync

# Install pre-commit hooks (runs ruff on commit)
uv run pre-commit install

# Load environment (production secrets, then dev/agent secrets)
export $(cat /etc/usa-wa/.env .env 2>/dev/null | xargs)

Dev server

Live service runs as systemd on port 8000 — never start uvicorn manually on that port. Use port 8001 (= API_PORT + 1) for the dev server so the live service stays up:

uv run uvicorn usa_wa_api.api.main:app --host 0.0.0.0 --port 8001 --reload --log-config packages/usa-wa-api/src/usa_wa_api/log_config.json

Reachable at https://usa-wa.exe.xyz:8001/ via the exe.dev proxy.

HTTP API

A read-only /api/v1 over the published datasets (#184, flipped to the serving schema at #313) — persons, organizations, roles, assignments (which are the tenure spans), sources, coverage and provenance chains — plus the unversioned probes (/health, /ready, /health/datasets, /health/serving) and the /datasets/* tree itself.

Read-only literally: since #313 the deployment registers no mutating route at all.

curl -s localhost:8001/api/v1/health/jobs | jq            # last run per job slug (#178)
curl -s localhost:8001/api/v1/sources/wa_sos_filings/coverage | jq   # declared vs verified (#180)
curl -s 'localhost:8001/api/v1/assignments?span_kind=chamber-house&as_of=2019-06-01' | jq
curl -s localhost:8001/openapi.json | jq '.paths | keys'

Full route inventory, the pagination contract and the identifier form: docs/API.md.

Tests

# Unit tier (#185) — no database at all; own coverage gate over packages/*/src/**
# (#198), so no flags needed. Add --no-cov for a faster, ungated inner loop
uv run pytest -m 'not db and not integration'

# Full suite (requires TEST_DATABASE_URL) — gated on the whole-tree floor
# (fail_under in pyproject.toml)
uv run pytest

# Single file (--no-cov: neither gate measures a slice)
uv run pytest --no-cov packages/usa-wa-api/tests/test_health.py

# Integration tests only (excluded by default)
uv run pytest -m integration

TEST_DATABASE_URL must be a dedicated test database, distinct from DATABASE_URL — the test conftest enforces this, and teardown drops each declared schema CASCADE (not Base.metadata.drop_all, which fails on the circular bill ↔ bill_version FKs).

Database migrations

Migrations need the owner role (DDL). In production, run the oneshot unit (alembic upgrade head + scripts/grants.sql under DATABASE_URL_OWNER): sudo systemctl start usa-wa-migrate. Ad-hoc alembic works when DATABASE_URL_OWNER is set (alembic/env.py prefers it over DATABASE_URL):

uv run alembic upgrade head
uv run alembic revision --autogenerate -m "description"

Lint

uv run ruff check .
uv run ruff format .

Full command reference: docs/COMMANDS.md — it carries the index of every operational CLI, grouped by the reference that documents each: succession, probes, roster PDF.

Agent-facing docs (architecture, per-package module maps, deployment, environment) are indexed under Detail Docs in AGENTS.md.

Deploy

The systemd units live under deploy/ — the live API, a migrate oneshot, and six timer-driven oneshots. (The PM sync sidecar and its three weekly committee reconcilers were four more until usa-wa#314 retired the PM sync stack, and the Postgres canonical tier's refreshes and invariant gates nine more until #412.)

Production secrets live in /etc/usa-wa/.env (managed manually on the VM, not in the repo) — this file must exist before enabling any unit, or migrate (owner DSN) and the services (app DSN) fail to start. The unit's ExecStartPre writes the current git SHA to /run/usa-wa/build-id and exposes it as BUILD_ID.

To install on a fresh host, copy all units, then enable in this order at provision time — migrate first, run synchronously by --now. Boot ordering is already enforced by the units (the API declares After=usa-wa-migrate.service, and migrate declares the reciprocal Before=; the timer-driven oneshots below carry the same After=), so a reboot can't serve against a not-yet-migrated schema. The --now here is for provision-time synchrony: it runs migrate to completion before you enable the services in the same session.

# Copy all units into systemd's path
sudo cp deploy/usa-wa*.{service,timer} /etc/systemd/system/
sudo systemctl daemon-reload

# 1. Migrate to head (owner role; RemainAfterExit oneshot — runs once now)
sudo systemctl enable --now usa-wa-migrate

# 2. Long-running service (app role)
sudo systemctl enable --now usa-wa

# Tail logs
sudo journalctl -u usa-wa -f

Scheduled units

The deploy also ships timer-driven oneshots; a fresh host must enable their timers explicitly — they are not pulled in by usa-wa.service. (The units above already landed in /etc/systemd/system/ via the usa-wa* copy.)

Enable every timer the block enables. The nightly pipeline is the one that matters most: its dbt tests and probes are the invariant gates, and they exit 1 and email the operator (OnFailure=, #49) when the data drifts — skip it and nothing fails, nothing alerts, and the absence looks identical to "no drift". The disk GC (#394) is the same bargain one layer down: it exits 1 when free space runs out, which is how a nightly publish dies on ENOSPC instead of publishing. The monthly roster re-check (#237) is the same bargain for a document revised about twice a decade: it archives nothing and exits 4 when a new edition is published — the alert is the only notice that one exists.

# Host hygiene (daily) — reclaim before the day's work, not after it fails
sudo systemctl enable --now usa-wa-disk-gc.timer                            # daily 05:45 UTC (#394)

# Dataset pipeline (daily) — the #302 publish chain
sudo systemctl enable --now usa-wa-pipeline.timer                           # daily 08:00 UTC (#311)

# Rollover measurement (daily, through 2027-02-28) — write-free; exit 4 = a WSL 2027-28 roster changed state
sudo systemctl enable --now usa-wa-wsl-availability-probe.timer             # daily 09:20 UTC (#135)

# Backup (daily) — the database dump + the raw store to GCS, create-only; needs the bucket,
# the key and the role first (docs/RECOVERY.md § Install and first run)
sudo systemctl enable --now usa-wa-backup.timer                             # daily 10:17 UTC (#434)

# Sweep (weekly)
sudo systemctl enable --now usa-wa-integrity-sweep.timer                    # weekly Sun 08:00 UTC

# Edition re-check (monthly) — dry-run fetch; exit 4 = new roster edition → operator email
sudo systemctl enable --now usa-wa-roster-pdf-recheck.timer                 # monthly 1st 09:00 UTC (#237)

sudo systemctl list-timers 'usa-wa-*'                                       # verify next-elapse

What each one does is in docs/DEPLOYMENT.md § Services. This block is pinned against deploy/*.timer by scripts/tests/test_docs_timer_drift.py (#167) — a new timer fails the suite until it is listed here with its own OnCalendar= cadence.

About

Washington State law, regulation, and policy tracking service

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages