The GUI pool has init=_setup_json_codec registered, which makes asyncpg
auto-serialize Python dicts to JSONB. Calling json.dumps() on a dict
before passing it to asyncpg double-encodes - the value gets stored as
a JSON-encoded string rather than a JSON object.
Changes:
- Remove json.dumps() from UPDATE statement in adapters_edit_submit
- Remove defensive isinstance(settings, str) checks that masked the bug
- Add regression tests to verify settings is passed as dict, not string
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add GET /adapters route for listing all adapters
- Add GET /adapters/{name} for edit form with per-adapter fields
- Add POST /adapters/{name} for validation, update, and audit
- Add ADAPTER_UPDATE audit constant
- Add Adapters nav link to base.html
- Server-side validation for cadence (60-3600), email format,
api_key_alias existence, satellites, and feed values
- Region displayed read-only with 1b-5 placeholder
- Hot reload via existing NOTIFY trigger (no new mechanism)
- Add comprehensive tests (9 tests)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- One durable consumer per event-bearing stream (CENTRAL_WX,
CENTRAL_FIRE, CENTRAL_QUAKE) for independent ack tracking
- max_deliver=5 prevents poison-message infinite loops
- Orphaned 'archive' consumer on CENTRAL_WX cleaned up on startup
- Consumer naming: archive-{stream_name_lower}
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add docs/test-database.md with one-time setup, DSN convention, reset
instructions, and explanation of why PostGIS is not in migrations
- Update docs/migrations.md with "Extensions are not in migrations"
section explaining superuser requirement
- Restore geom GEOMETRY(Geometry, 4326) column to test fixture now that
central_test has PostGIS installed
- Add CREATE EXTENSION IF NOT EXISTS postgis to test fixture for
self-bootstrap (central_test is superuser)
- Add Testing section to README.md pointing to docs/test-database.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace pytest.skip stubs with actual DB tests against central_test
- Test backfill for all three adapters (nws, firms, usgs_quake)
- Test FK RESTRICT, NOT NULL, and FK validation constraints
- Test schema changes (source dropped, adapter exists with constraints)
- Delete stale sql/schema.sql (migrations are sole source of truth)
- Update docs/migrations.md with schema.sql removal note
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaces module-path-based source column (e.g. "central/adapters/nws")
with stable adapter identifier (e.g. "nws") that foreign-keys to
config.adapters.name.
Migration 011:
- ADD COLUMN adapter TEXT
- Backfill via REPLACE(source, 'central/adapters/', '')
- SET NOT NULL + FK RESTRICT
- CREATE INDEX (adapter, received DESC) for dashboard queries
- DROP COLUMN source
Code changes:
- Event model: source field renamed to adapter
- All adapters: use adapter="name" instead of source="central/adapters/name"
- Archive: write adapter column instead of source
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The library's validate_csrf expects the raw token in the form and
the signed token in the cookie. Previously we were putting the signed
token in both places, which caused signature mismatch errors.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The library supports form-data tokens via token_location="body" and
token_key config options, which we missed in the initial integration.
Removed hand-rolled _validate_csrf_form helper in favor of the
library's validate_csrf method.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add _validate_csrf_form helper for form-based CSRF token validation
(compares form csrf_token with fastapi-csrf-token cookie)
- Fix index route to pass operator and csrf_token to template context
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- test_gui_scaffold.py: use standalone router instead of importing app
to avoid triggering settings load during test collection
- test_setup_gate.py: expect 302 (not 307) for setup gate redirect
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Revert uvicorn port from 8088 to 8000 (1b-1 pinned value)
- Change SetupGateMiddleware redirect from 307 to 302 for consistency
with all other redirects in the codebase
Port 8000 confirmed free on CT104. Earlier change to 8088 was
incorrect — 8080 is held by NATS WebSocket, not 8000.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add migrations 007-010 for system config, operators, sessions, audit_log
- Implement argon2id password hashing via argon2-cffi
- Implement session-based authentication with database-stored tokens
- Add SetupGateMiddleware to redirect to /setup until first operator created
- Add SessionMiddleware to load session from cookie and attach operator
- Create /setup, /login, /logout, /change-password routes with CSRF protection
- Add periodic session cleanup task (hourly)
- Add audit logging for auth events
- Update systemd unit with EnvironmentFile for /etc/central/central.env
- Add comprehensive tests for auth, middleware, and audit modules
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- FastAPI app with Jinja2 templates and Pico CSS + HTMX from CDN
- Routes: GET / (placeholder page), GET /health (JSON healthcheck)
- systemd unit (no Install section - manual start only)
- TestClient tests for both endpoints
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update subject_for_event to handle quake.* category events.
Subject format: central.quake.event.<magnitude_tier>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
USGS Earthquake Hazards Program adapter:
- Polls GeoJSON feed (all_hour default, configurable)
- Magnitude tier classification (minor/light/moderate/strong/major/great)
- Deduplication via USGS stable event ID
- Region filter via shapely point-in-bbox
- Skips events with null magnitude (quarry blasts, etc.)
Includes comprehensive unit tests.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add _ADAPTER_REGISTRY dict for adapter class lookup
- Unify adapter __init__ signatures (all take config, config_store, cursor_db_path)
- NWSAdapter now accepts config_store param (unused, for signature uniformity)
- Adding new adapters requires only one dict entry, no supervisor changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
NASA FIRMS adapter for VIIRS satellite fire detections:
- Polls VIIRS_SNPP_NRT and VIIRS_NOAA20_NRT satellites
- Deduplication via stable ID (satellite📅time:lat:lon)
- Hot-reload support for region, satellites, and API key
- Confidence mapping: l/n/h -> low/nominal/high
- Severity: high=3, nominal=2, low=1
Includes comprehensive unit tests for:
- CSV parsing and event generation
- Deduplication logic
- URL building and config application
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add shapely dependency for geometry intersection
- Replace _point_in_region with _geometry_intersects_region
- Uses Shapely shape() and box() for proper GeoJSON handling
- Avoids false negatives on large alert polygons
Also adds antimeridian-crossing bbox rejection to RegionConfig validator.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove states array from NWS settings
- Add region bbox covering ID/OR/WA/MT/WY/UT/NV
- Bbox: north=49.5, south=31.0, east=-102.0, west=-124.5
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- config.streams table for JetStream retention config
- Column-filtered NOTIFY: only fires on max_age_s changes
- Prevents self-loop when supervisor updates max_bytes
- Seeds CENTRAL_WX (7d/10GB) and CENTRAL_META (1d/100MB)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add RegionConfig pydantic model with validators
- NWSAdapter now uses bbox for client-side alert filtering
- Implement apply_config for hot-reload of region changes
- Remove states-based filtering logic
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SourceAdapter now requires apply_config() for hot-reload support.
Each adapter implements its own config extraction from settings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>