Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="frontend/public/routeforge.png" alt="RouteForge Logo" width="420">
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-v0.9.0--rc-blue" alt="Version">
<img src="https://img.shields.io/badge/version-v0.9.1--rc-blue" alt="Version">
<img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-orange" alt="License">
<img src="https://img.shields.io/badge/status-beta-yellow" alt="Status">
<img src="https://img.shields.io/badge/selfhosted-ready-success" alt="Selfhosted">
Expand Down Expand Up @@ -43,7 +43,7 @@ Routing changes often require fast but traceable checks across multiple external

## Current Alpha Status

RouteForge is a **functional beta** release with production-like workflows for read-only validation and demo usage. Current release target: **v0.8.1-beta**.
RouteForge is a **functional beta** release with production-like workflows for read-only validation and demo usage. Current release target: **v0.9.1-rc**.

## Quickstart with Docker Compose

Expand Down
27 changes: 27 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
## v0.9.1-rc (2026-05-20)

### Motivation
Finalize release-candidate validation for deployment, upgrade discipline, security posture, and role-based UX quality before v1.0.

### Implemented Changes
- Added deployment health check script (`backend/scripts/check_deployment_health.py`) for API/system/database/build smoke validation.
- Expanded operations docs with explicit release QA, upgrade QA, Docker QA, and security checklist guidance.
- Updated release checklist with role/feature UX validation scenarios for admin/operator/viewer.
- Version bump across backend/frontend/docs to `0.9.1` / `v0.9.1-rc`.

### Deployment QA Notes
- Use `python backend/scripts/check_deployment_health.py --base-url http://localhost:8000 --check-setup`.
- Ensure `/api/system/status` reports `read_only=true` and `migration_status` not `behind`.

### Security QA Notes
- Verify `COOKIE_SECURE=true` for HTTPS.
- Use explicit `CORS_ORIGINS`, never `*` in production.
- Ensure Alembic current/head parity before go-live.

### Testing
- `cd backend && pytest -q`
- `cd frontend && npm run build`

### Known Limitations
- Deployment smoke script checks endpoint health/state only; it does not perform synthetic business transactions.


## v0.9.0-rc (2026-05-20)

Expand Down
12 changes: 6 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# RouteForge Roadmap

## Current Status
v0.9.0-rc, BGP Visibility Details completed, read-only
v0.9.1-rc, BGP Visibility Details completed, read-only

## v0.9.0-rc
## v0.9.1-rc
- projects/change cases
- grouped preflight reports

## v0.9.0-rc
## v0.9.1-rc
- bgp visibility details

## v0.9.0-rc
## v0.9.1-rc
- roa planner / roa preflight

## v0.9.0-rc
## v0.9.1-rc
- watch mode / scheduled rechecks

## v0.9.0-rc
## v0.9.1-rc
- security review
- UX review
- API stability
Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/routes_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@

@router.get('/health')
def health() -> dict:
return {"status": "ok", "version": "v0.9.0-rc", "database": get_database_status(engine).get("status", "unknown")}
return {"status": "ok", "version": "v0.9.1-rc", "database": get_database_status(engine).get("status", "unknown")}
2 changes: 1 addition & 1 deletion backend/app/api/routes_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
def system_info():
return {
'name': 'RouteForge',
'version': 'v0.9.0-rc',
'version': 'v0.9.1-rc',
'demo_mode': settings.demo_mode,
'read_only': True,
'data_sources': ['RIPEstat', 'RIPEstat Whois/Registry'],
Expand Down
2 changes: 1 addition & 1 deletion backend/app/core/system_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def build_system_status(engine: Engine | None) -> dict:
return {
"status": "ok",
"name": settings.app_name,
"version": "v0.9.0-rc",
"version": "v0.9.1-rc",
"read_only": True,
"mode": "demo" if settings.demo_mode else "live",
"demo_mode": settings.demo_mode,
Expand Down
2 changes: 1 addition & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
logging.basicConfig(level=getattr(logging, settings.log_level.upper(), logging.INFO))
logger = logging.getLogger("routeforge")

app = FastAPI(title="RouteForge", version="0.9.0")
app = FastAPI(title="RouteForge", version="0.9.1")

app.add_middleware(
CORSMiddleware,
Expand Down
2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "routeforge-backend"
version = "0.9.0"
version = "0.9.1"
description = "RouteForge backend"
license = "AGPL-3.0-or-later"
requires-python = ">=3.12"
Expand Down
87 changes: 87 additions & 0 deletions backend/scripts/check_deployment_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""RouteForge deployment smoke/health check for release validation."""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import urlopen


def fetch_json(url: str, timeout: float) -> dict:
try:
with urlopen(url, timeout=timeout) as resp:
return json.loads(resp.read().decode("utf-8"))
except HTTPError as exc:
raise RuntimeError(f"HTTP {exc.code} for {url}") from exc
except URLError as exc:
raise RuntimeError(f"Connection error for {url}: {exc.reason}") from exc


def check(condition: bool, message: str, failures: list[str]) -> None:
prefix = "[OK]" if condition else "[FAIL]"
print(f"{prefix} {message}")
if not condition:
failures.append(message)


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--base-url", default="http://localhost:8000", help="Backend base URL")
parser.add_argument("--frontend-dist", default="frontend/dist", help="Frontend build artifact directory")
parser.add_argument("--timeout", type=float, default=5.0)
parser.add_argument("--check-setup", action="store_true", help="Also validate /api/setup/required endpoint")
args = parser.parse_args()

base = args.base_url.rstrip("/")
failures: list[str] = []

try:
health = fetch_json(f"{base}/health", timeout=args.timeout)
except RuntimeError as exc:
check(False, str(exc), failures)
print(f"\nDeployment check failed with {len(failures)} issue(s).")
return 1
check(health.get("status") == "ok", "Backend /health is reachable and status=ok", failures)

try:
status = fetch_json(f"{base}/api/system/status", timeout=args.timeout)
except RuntimeError as exc:
check(False, str(exc), failures)
print(f"\nDeployment check failed with {len(failures)} issue(s).")
return 1
check(status.get("status") == "ok", "System status endpoint reachable", failures)
check(bool(status.get("version")), "System status contains version", failures)
check(status.get("read_only") is True, "System status read_only=true", failures)

database = status.get("database") if isinstance(status, dict) else None
check(isinstance(database, dict), "System status contains database payload", failures)
if isinstance(database, dict):
check(bool(database.get("status")), "Database status is present", failures)
check(bool(database.get("schema_version")), "Database schema_version is present", failures)
check(bool(database.get("migration_head")), "Database migration_head is present", failures)
check(database.get("migration_status") != "behind", "Migration status is not behind", failures)

dist = Path(args.frontend_dist)
check(dist.exists(), f"Frontend artifacts directory exists ({dist})", failures)
check((dist / "index.html").exists(), f"Frontend build artifact exists ({dist / 'index.html'})", failures)

if args.check_setup:
try:
setup = fetch_json(f"{base}/api/setup/required", timeout=args.timeout)
check("required" in setup, "Setup endpoint reachable and contains required field", failures)
except RuntimeError as exc:
check(False, str(exc), failures)

if failures:
print(f"\nDeployment check failed with {len(failures)} issue(s).")
return 1
print("\nDeployment check completed successfully.")
return 0


if __name__ == "__main__":
sys.exit(main())
2 changes: 1 addition & 1 deletion backend/tests/test_api_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def test_system_status_endpoint() -> None:
response = client.get('/api/system/status')
assert response.status_code == 200
payload = response.json()
assert payload.get('version') == 'v0.9.0-rc'
assert payload.get('version') == 'v0.9.1-rc'
assert payload.get('read_only') is True
assert payload.get('database', {}).get('status')
assert payload.get('ripestat', {}).get('cache_ttl_seconds') is not None
Expand Down
53 changes: 15 additions & 38 deletions docs/operations/database-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,37 @@

RouteForge uses Alembic for schema lifecycle tracking from `v0.5.3-beta`.

## Why
- Traceable schema history
- Safe, repeatable upgrades
- Visible migration state in system status

## Engines
- Production: PostgreSQL (recommended)
- Dev/demo: SQLite supported

## Commands
```bash
docker compose exec backend alembic current
docker compose exec backend alembic heads
docker compose exec backend alembic upgrade head
```

## Existing pre-baseline databases
If the schema already exists from historical `create_all`, mark baseline first:
```bash
docker compose exec backend alembic stamp 0001_initial_schema
docker compose exec backend alembic upgrade head
```

## Auto migration
`ROUTEFORGE_AUTO_MIGRATE` is not enabled by default in this beta.
Run migrations manually before production upgrades.

## Troubleshooting: missing `created_by_user_id` after v0.6 upgrade

Fehlerbild:

`sqlite3.OperationalError: table checks has no column named created_by_user_id`

Ursache:

Die Migration `0002_users_and_report_ownership` wurde auf einer bestehenden Datenbank nicht ausgeführt.

Fix:
## Docker QA runbook (v0.9.1-rc)

```bash
docker compose up -d
docker compose logs backend
docker compose exec backend alembic current
docker compose exec backend alembic heads
docker compose exec backend alembic upgrade head
docker compose restart backend
```

Wenn bestehende Pre-Alembic DB:
Validation goals:
- backend comes up without migration crash
- `alembic current` equals `alembic heads`
- after restart, `/api/system/status` remains `migration_status=up_to_date`

```bash
docker compose exec backend alembic stamp 0001_initial_schema
docker compose exec backend alembic upgrade head
```
## Reverse proxy notes

Vorher Backup:
Use a reverse proxy for production TLS termination and same-origin `/api` forwarding.
See `docs/operations/reverse-proxy.md` for deployment examples.

## Existing pre-baseline databases
If schema already exists from historical `create_all`, mark baseline first only after schema verification:
```bash
docker compose exec backend sh -c 'cp /app/data/routeforge.db /app/data/routeforge.db.bak.$(date +%s)'
docker compose exec backend alembic stamp 0001_initial_schema
docker compose exec backend alembic upgrade head
```
54 changes: 43 additions & 11 deletions docs/operations/release-checklist.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,58 @@
# Release Checklist

## Before release
## v0.9.1-rc: Deployment QA & UX Validation

### 1) Pre-release validation

- `git pull`
- Run backend tests (`cd backend && pytest -q`)
- Run frontend production build (`cd frontend && npm run build`)
- Validate Compose (`docker compose config` and production config)
- Check system status endpoint (`/api/system/status`)
- Check migration status visibility (`database.schema_version`, `migration_head`, `migration_status`)

## Tagging
### 2) Deployment smoke check

Run after backend/frontend are up and frontend artifacts were built:

```bash
python backend/scripts/check_deployment_health.py --base-url http://localhost:8000 --check-setup
```

The script validates:
- Backend reachable (`/health`)
- `/api/system/status` reachable
- `version` present
- `read_only=true`
- `database.status`, `schema_version`, `migration_head` present
- `migration_status` is not `behind`
- Frontend build artifacts exist (`frontend/dist/index.html`)
- Optional `/api/setup/required` reachability

### 3) UX QA checklist

- [ ] Setup Flow works and initial admin creation succeeds
- [ ] Login/Logout works end-to-end
- [ ] User Management roles behave correctly (admin/operator/viewer)
- [ ] Audit Log is visible only for admin users
- [ ] Change Case create/edit/delete works for authorized roles
- [ ] BGP Visibility check works and stores report
- [ ] ROA Planner check works and stores report
- [ ] Watch Target create/edit/run-due works for authorized roles
- [ ] Report export works for Markdown/HTML/Summary
- [ ] Viewer can read data but cannot execute checks/watch/change operations
- [ ] Operator can execute checks/watch/change cases, but cannot access user/audit admin-only features

### 4) Tagging

- `git tag -a vX.Y.Z[-beta] -m "RouteForge vX.Y.Z[-beta]"`
- `git push origin vX.Y.Z[-beta]`
- `git tag -a v0.9.1-rc -m "RouteForge v0.9.1-rc"`
- `git push origin v0.9.1-rc`

## GitHub Release
### 5) GitHub Release

- Use release title matching the version and sprint scope
- Enable **prerelease** checkbox for beta versions
- Include structured release notes with highlights and known limitations
- Release title aligned with `v0.9.1-rc`
- Mark as prerelease
- Include deployment, upgrade, and security QA notes

## Post-release smoke test
### 6) Post-release smoke test

- `GET /health`
- `GET /api/system/info`
Expand Down
Loading
Loading