Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
86abe08
ML Analytics: validation metrics, feature importance, backtest, tab s…
Apr 24, 2026
c86048d
handover Day 1: alert routes, DB health check, env vars, pan-African …
Apr 24, 2026
c19c8a1
handover Day 2: deterministic reports — remove every random.* from re…
Apr 24, 2026
d839130
handover Day 3: reports sections.* + alerts unified source + real POS…
Apr 24, 2026
ad0734b
handover Day 4: scheduler leader election + gate create_all() on dev env
Apr 24, 2026
86a86c4
handover Day 5-6: alerts ack workflow + severity sort + reports water…
Apr 24, 2026
9b0f27c
handover Week 3: audit_log → DB, filled operator pack, env.prod.example
Apr 24, 2026
e1b44f1
handover: finish deterministic coverage, acceptance tests, readiness CLI
Apr 24, 2026
32b339d
handover: finish post-v1 roadmap — alerts DB, model compare, fairness
Apr 24, 2026
f444381
handover: drop the stale 'skeleton' note at the top of the operator pack
Apr 24, 2026
a04799c
handover: security + ML honesty pass per audit
Apr 25, 2026
151e10d
handover: drop the stale commit hash from the operator pack header
Apr 25, 2026
789b517
env: 12-month MODIS LST animation (Ethiopia → /environmental)
Apr 25, 2026
40ba36a
env: country-border clip + Monthly/Yearly toggle on LST animation
Apr 25, 2026
5d453ed
env: LST animation default 0.25° + Resolution dropdown
Apr 25, 2026
db17c91
env: water-balance dashboard, Manager's Read, reservoir what-if, Hydr…
Apr 27, 2026
c10c1ed
transboundary: Cross-Border Flux tab + Soman et al. 2026 SWOT QC chain
May 11, 2026
bbae178
cache: refresh GEE CHIRPS grid + SWOT Hydrocron v2 snapshots
May 12, 2026
e1d748d
reports: status-doc generator + April 2026 progress and status reports
May 12, 2026
a4a647b
ml: separate ground-truth vs pseudo-label rows in WQ training (roadma…
May 15, 2026
b9456c1
ml: baseline benchmarks + beats_empirical promotion gate (roadmap §7)
May 15, 2026
63c5b64
nisar: staleness gate so cached soil-moisture re-fetches live data
May 15, 2026
1e5b6b3
soil-moisture: data-driven NISAR provenance banner + fix cold-load ti…
May 16, 2026
f4f50b0
nisar: real per-station soil-moisture time series via cloud byte-range
May 16, 2026
ee2ebb0
recent-water: surface Landsat-8/9, Dynamic World, SAR layers in map UI
May 27, 2026
6c3ee90
admin: /admin/ml/baselines panel for roadmap §7 promotion gate
May 28, 2026
2ae2e8c
discharge: replace stale TODOs with cross-references to real SWOT path
May 28, 2026
4a9064b
demo_restart: source .env before launching uvicorn
May 28, 2026
76537b5
ml: split-conformal prediction intervals for WQ models (roadmap §3)
May 28, 2026
175a06e
ml: PSI drift monitor + conformal/drift surfaces in admin (§6, §3 pol…
May 28, 2026
6ec45c2
ml: canonical spatiotemporal feature store + WQ backfill ETL (roadmap…
May 28, 2026
65bfaae
ml: route WQ training through the feature store; admin stats endpoint
May 28, 2026
11c3a6b
backend audit (roadmap §3): tests, prompt caching, RBAC, dead-code cl…
May 29, 2026
48b5362
swot: test the KaRIn wide-swath lake surface-area pipeline (roadmap §9)
May 29, 2026
eee310d
nisar: label Blue Nile pilot 'Real data (200 m)' when live (roadmap §8)
May 29, 2026
7a4d1dd
provenance: DataProvenance + citations on Hydrology/Environmental/ML/…
May 29, 2026
0639c94
hooks: extract useWaterBody + migrate WaterBodyDetail to it (roadmap §7)
May 29, 2026
976495f
export: reusable ExportMenu (CSV/GeoJSON/PDF), wire the stub hubs (ro…
May 29, 2026
fd78319
story: 90-second guided Blue Nile walkthrough at /story (roadmap §11)
May 29, 2026
681eeac
a11y: enforce WCAG 2.1 AA color-contrast app-wide + remediate (roadma…
May 29, 2026
bb18b95
test: guard Historical WSE chart overlay toggles (roadmap §10)
May 29, 2026
1429206
i18n: extend locales beyond nav + parity guard, wire a hub title (roa…
May 29, 2026
b8ae226
docs: flagship-basin UAT demo runbook (roadmap §1)
May 29, 2026
3e43dda
test: fix WQ label-kind tests shadowed by the feature store
May 29, 2026
ac20174
swot: PLD/SWORD crosswalk separating observed vs modeled coverage
May 29, 2026
4e99cb7
alerts: acknowledgement workflow + per-alert deep-links
May 29, 2026
adb912d
frontend: i18n hub copy + chart tooltips, useApiResource adoption 4→2…
May 29, 2026
b836626
deploy: TLS staging overlay + audit_log auto-migration + CORS hardeni…
May 29, 2026
dd71cad
backend + map: SWOT crosswalk integration, GRACE/PO.DAAC freshness, d…
May 29, 2026
dbbb425
handover: tick §9 readiness lines that the May work just made true
May 29, 2026
e8d45d4
ops: health endpoint HTTP status codes + missing sub-routes + ETL ena…
May 29, 2026
0d8742e
cache: refresh seed data across river altimetry, GEE, NISAR, WQ, SWOT
May 29, 2026
5a5523a
handover: annotate §9 health-endpoint line with the safety fixes
May 29, 2026
42564ea
ops: address May 30 readiness audit — safe defaults, gates, backups, …
May 29, 2026
b9cc6b0
ops: close second readiness-audit pass (#1, #2 conftest, #4 — #8)
May 29, 2026
3c24db2
ml: fairness-by-biome reporting (post-handover roadmap item #6)
May 30, 2026
c86ebdf
deps: bump axios + override ws to clear Dependabot alerts
Jun 2, 2026
15f4984
docs: add Water Hub tool-description doc (parallels trade hub)
Jun 11, 2026
c0c7751
add handover readiness and water stress intelligence
Jun 25, 2026
b6ca017
close June handover issues in readiness app
Jun 26, 2026
a079c9f
update June handover report
Jun 26, 2026
fa8df2a
rename June Water Hub report docx
Jun 26, 2026
4fd199e
remove resolved issues from June report
Jun 26, 2026
71314cd
handover: pre-handover review fixes (i18n, doc accuracy, hardening)
Jun 26, 2026
3526660
cache: refresh demo seed data (NISAR, WQ, GEE, alerts, audit)
Jun 26, 2026
88a74e9
feat: cloud-native geospatial capabilities adapted from GeoLibre
Jun 27, 2026
ba931af
release: prepare Water Hub submission candidate
Jul 27, 2026
41438eb
release: attest Water Hub source snapshot
Jul 27, 2026
e5719b7
docs: add Google Cloud resource requirements
Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
85 changes: 81 additions & 4 deletions .env.prod.example
Original file line number Diff line number Diff line change
@@ -1,29 +1,106 @@
# Production secrets for docker-compose.prod.yml. Copy to .env.prod and fill in.
# NEVER commit .env.prod — it's covered by .gitignore (add there if missing).
# NEVER commit .env.prod — it's covered by .gitignore.

# ── Database ──────────────────────────────────────────────────────────
POSTGRES_USER=water_hub
POSTGRES_PASSWORD= # generate a strong password
POSTGRES_DB=water_hub

# Redis is isolated on an internal network and also requires authentication.
# URL-encode reserved URI characters if this value is ever used outside Compose.
REDIS_PASSWORD=

# ── Backend ───────────────────────────────────────────────────────────
# Generate with: python -c "import secrets; print(secrets.token_urlsafe(64))"
SECRET_KEY=
# Random token for internal metrics/readiness detail. Generate like SECRET_KEY.
INTERNAL_METRICS_TOKEN=
# Comma-separated origins the backend trusts for CORS
CORS_ORIGINS=https://waterhub.example.com

# Optional third-party credentials
# ── LLM providers (optional — leave blank to disable policy briefs) ───
# Primary: Anthropic Claude
ANTHROPIC_API_KEY=
# Alternative: Google Gemini (free tier available)
GEMINI_API_KEY=

# ── Google Earth Engine (optional but required for fresh satellite data)
GEE_SERVICE_ACCOUNT_EMAIL=
GEE_PROJECT_ID=
GEE_CREDENTIALS_JSON= # absolute path inside the backend container
# Either inline the key JSON here (preferred in Docker — single-line string)
# or leave this blank and mount the file + set GEE_PRIVATE_KEY_FILE.
GEE_CREDENTIALS_JSON=

# ── NASA Earthdata (optional — only for ICESat-2 ATL13 altimetry) ─────
# Register at https://urs.earthdata.nasa.gov/
EARTHDATA_USERNAME=
EARTHDATA_PASSWORD=

# ── DAHITI altimetry validator (optional — paid tier only) ───────────
# Register at https://dahiti.dgfi.tum.de/
DAHITI_API_KEY=

# ── Frontend build args (inlined into the static bundle) ──────────────
# Leave empty to use same-origin requests (nginx proxies /api to backend:8000)
VITE_API_URL=
VITE_USE_DEMO_API=false
VITE_SENTRY_DSN=
VITE_APP_VERSION= # e.g. a git sha
VITE_APP_VERSION= # e.g. a git sha or "handover-un-v1"

# ── Container runtime ─────────────────────────────────────────────────
FRONTEND_PORT=80
# Immutable image tag (normally the release git SHA or signed release tag).
IMAGE_TAG=local
WATERHUB_BACKEND_IMAGE=waterhub-backend
WATERHUB_FRONTEND_IMAGE=waterhub-frontend
COMPOSE_PROJECT_NAME=waterhub
# Dedicated frontend→backend proxy subnet. Change if it overlaps a host/VPN
# route; the same value becomes WATERHUB_TRUSTED_PROXY_NETWORKS in the API.
WATERHUB_APP_SUBNET=172.30.20.0/24

# ── Scheduled work (APScheduler ETL) ──────────────────────────────────
# True in production so NISAR refresh, alert scans, and feature-store
# backfill actually run. Set to false for read-only demo hosts.
ETL_ENABLED=true
ETL_SCHEDULE_HOURS=24

# ── Recent-water surface layers (optional) ────────────────────────────
# GEE asset paths for the post-2022 surface-water overlays. Each layer
# stays hidden in the UI until its asset path is populated. Bake the
# assets first with backend/app/scripts/build_landsat_recent_asset.py
# and the equivalent scripts for Dynamic World / Sentinel-1 SAR water.
WATERHUB_RECENT_WATER_ASSET=
WATERHUB_SAR_WATER_ASSET=
WATERHUB_LANDSAT_RECENT_ASSET=

# ── Self-registration ────────────────────────────────────────────────
# Default false on a UN-facing deployment — operators add users via the
# admin route. Set to true only for workshop / hackathon flows.
ALLOW_SELF_REGISTRATION=false
ENABLE_DEMO_API=false
# Optional workshop login: keep disabled in ordinary production. When enabled,
# this must name a pre-provisioned active viewer account (never an admin).
ENABLE_DEMO_SESSION=false
DEMO_SESSION_USER_EMAIL=
DEMO_SESSION_EXPIRE_MINUTES=15
# Institutional data controller must approve this maximum before deployment.
AUDIT_RETENTION_DAYS=90

# Database pool is per API worker. With four workers the defaults allow at
# most 40 application connections. Size against Postgres max_connections or
# put PgBouncer in transaction mode for larger/horizontal deployments.
DATABASE_POOL_SIZE=5
DATABASE_MAX_OVERFLOW=5

# ── Encrypted backups ────────────────────────────────────────────────
# An age recipient (for example age1...) enables encryption at rest.
BACKUP_AGE_RECIPIENT=
# Production policy should set this true; false is useful only for local drills.
WATERHUB_BACKUP_REQUIRE_ENCRYPTION=true

# ── TLS overlay (docker-compose.tls.yml) ──────────────────────────────
# Only needed when you bring up the stack with -f docker-compose.tls.yml.
# Caddy will obtain a Let's Encrypt cert for $WATERHUB_DOMAIN automatically.
# ACME_EMAIL is used by Let's Encrypt for expiry warnings and rate-limit identity.
WATERHUB_DOMAIN=waterhub.example.com
ACME_EMAIL=ops@example.com
21 changes: 21 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Change and risk

Describe the outcome, affected users/data, scientific and security risk.

## Verification

- [ ] Backend tests/lint
- [ ] Frontend lint/unit/build/E2E where applicable
- [ ] API contract and migration checks
- [ ] Clean-volume container smoke where deployment changes
- [ ] Dependency, secret and license review

## Data and provenance

- [ ] No runtime caches, logs, credentials, personal data or model binaries added
- [ ] Observed/modelled/synthetic/unavailable status remains explicit
- [ ] Dataset source, license, checksum and lineage updated where applicable

## Deployment

State configuration/migration changes, rollback method and backup requirement.
79 changes: 71 additions & 8 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ on:
push:
paths:
- "backend/**"
- "docker-compose*.yml"
- "deploy/**"
- "docs/governance/source-manifest.json"
- ".github/workflows/backend-ci.yml"
pull_request:
paths:
- "backend/**"
- "docker-compose*.yml"
- "deploy/**"
- "docs/governance/source-manifest.json"
- ".github/workflows/backend-ci.yml"

jobs:
Expand All @@ -22,16 +28,15 @@ jobs:
with:
python-version: "3.11"

- name: Install flake8
run: pip install flake8
- name: Install Ruff
run: pip install ruff==0.12.4

- name: Lint with flake8
- name: Lint for runtime errors
working-directory: backend
run: |
# Stop build on syntax errors or undefined names
flake8 app/ --count --select=E9,F63,F7,F82 --show-source --statistics
# Warn on style issues (non-blocking)
flake8 app/ --count --max-line-length=120 --statistics --exit-zero
run: ruff check app tests --select E9,F63,F7,F82

- name: Validate source lineage and reference checksums
run: python deploy/validate-source-manifest.py

test:
runs-on: ubuntu-latest
Expand All @@ -56,3 +61,61 @@ jobs:
env:
PYTEST_DISABLE_PLUGIN_AUTOLOAD: "1"
run: pytest -q tests

postgres-integration:
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: postgis/postgis:15-3.3
env:
POSTGRES_USER: waterhub_ci
POSTGRES_PASSWORD: waterhub_ci_password
POSTGRES_DB: waterhub_ci
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U waterhub_ci -d waterhub_ci"
--health-interval 10s
--health-timeout 5s
--health-retries 10
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 10
env:
DATABASE_URL: postgresql://waterhub_ci:waterhub_ci_password@127.0.0.1:5432/waterhub_ci
REDIS_URL: redis://127.0.0.1:6379/0
REDIS_REQUIRED: "true"
SECRET_KEY: ci-only-secret-key-with-at-least-thirty-two-characters
CORS_ORIGINS: https://waterhub-ci.example.invalid
ENVIRONMENT: production
ETL_ENABLED: "false"
SCHEDULER_MODE: external
ALLOW_SYNTHETIC_DATA: "false"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: backend/requirements.txt
- name: Install backend
working-directory: backend
run: pip install -r requirements.txt
- name: Upgrade an empty PostGIS database
working-directory: backend
run: alembic upgrade head
- name: Verify exact migration head and reference manifest
working-directory: backend
run: |
set +e
python -m app.scripts.readiness_check --skip-http
result=$?
set -e
test "$result" -le 1
6 changes: 5 additions & 1 deletion .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "22"
cache: "npm"
cache-dependency-path: frontend/package-lock.json

Expand All @@ -33,6 +33,10 @@ jobs:
working-directory: frontend
run: npm run lint

- name: Verify frontend routes against the v1 OpenAPI contract
working-directory: frontend
run: npm run api:contract

- name: Unit tests (vitest)
working-directory: frontend
run: npm test
Expand Down
64 changes: 64 additions & 0 deletions .github/workflows/release-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Release image smoke

on:
push:
paths:
- "backend/**"
- "frontend/**"
- "deploy/**"
- "docker-compose*.yml"
- ".env.prod.example"
- ".github/workflows/release-ci.yml"
pull_request:
paths:
- "backend/**"
- "frontend/**"
- "deploy/**"
- "docker-compose*.yml"
- ".env.prod.example"
- ".github/workflows/release-ci.yml"

permissions:
contents: read

jobs:
clean-host-smoke:
runs-on: ubuntu-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@v4
- name: Validate source lineage and reference checksums
run: |
python deploy/validate-source-manifest.py --require-approval
python deploy/validate-release-approvals.py
- name: Validate shell scripts
run: bash -n deploy/*.sh && sh -n backend/docker/*.sh
- name: Validate, build and smoke a fresh stack
run: ./deploy/clean-volume-smoke.sh

image-security:
runs-on: ubuntu-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@v4
- name: Build backend release image
run: docker build -t waterhub-backend:security backend
- name: Scan backend image
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: waterhub-backend:security
format: table
severity: CRITICAL,HIGH
ignore-unfixed: true
exit-code: "1"
- name: Generate CycloneDX SBOM
uses: anchore/sbom-action@v0.17.2
with:
image: waterhub-backend:security
format: cyclonedx-json
output-file: waterhub-backend.sbom.json
- uses: actions/upload-artifact@v4
with:
name: waterhub-backend-sbom
path: waterhub-backend.sbom.json
retention-days: 30
65 changes: 65 additions & 0 deletions .github/workflows/security-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Dependency and secret security

on:
push:
branches: [main, master, "handover/**"]
paths:
- "backend/requirements.txt"
- "frontend/package*.json"
- "package*.json"
- ".github/workflows/security-ci.yml"
pull_request:
schedule:
- cron: "17 4 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
python-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install pip-audit==2.9.0
- working-directory: backend
run: pip-audit --requirement requirements.txt --strict

node-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: |
package-lock.json
frontend/package-lock.json
- run: npm audit --audit-level=high
- working-directory: frontend
run: npm audit --audit-level=high

secret-history:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

dependency-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
Loading
Loading