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.1--rc-blue" alt="Version">
<img src="https://img.shields.io/badge/version-v0.9.2--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.9.1-rc**.
RouteForge is a **functional beta** release with production-like workflows for read-only validation and demo usage. Current release target: **v0.9.2-rc**.

## Quickstart with Docker Compose

Expand Down
6 changes: 3 additions & 3 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
## v0.9.1-rc (2026-05-21)
## v0.9.2-rc (2026-05-21)

- UI Cleanup & English-only polish

## v0.9.1-rc (2026-05-20)
## v0.9.2-rc (2026-05-20)

### Motivation
Finalize release-candidate validation for deployment, upgrade discipline, security posture, and role-based UX quality before v1.0.
Expand All @@ -11,7 +11,7 @@ Finalize release-candidate validation for deployment, upgrade discipline, securi
- 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`.
- Version bump across backend/frontend/docs to `0.9.2` / `v0.9.2-rc`.

### Deployment QA Notes
- Use `python backend/scripts/check_deployment_health.py --base-url http://localhost:8000 --check-setup`.
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.1-rc, BGP Visibility Details completed, read-only
v0.9.2-rc, BGP Visibility Details completed, read-only

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

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

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

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

## v0.9.1-rc
## v0.9.2-rc
- security review
- UX review
- API stability
Expand Down
12 changes: 6 additions & 6 deletions backend/app/api/routes_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def check_asn(payload: AsnCheckRequest, db: Session = Depends(get_db), user=Depe
except HTTPException:
raise
except Exception as exc:
raise HTTPException(status_code=500, detail=f"ASN-Prüfung fehlgeschlagen: {exc}") from exc
raise HTTPException(status_code=500, detail=f"ASN check failed: {exc}") from exc


@router.post('/asn-rpki', response_model=CheckResponse)
Expand All @@ -37,7 +37,7 @@ def check_asn_rpki(payload: AsnRpkiBatchRequest, db: Session = Depends(get_db),
except HTTPException:
raise
except Exception as exc:
raise HTTPException(status_code=500, detail=f"ASN-RPKI-Batchprüfung fehlgeschlagen: {exc}") from exc
raise HTTPException(status_code=500, detail=f"ASN RPKI batch check failed: {exc}") from exc


@router.post('/prefix', response_model=CheckResponse)
Expand All @@ -48,7 +48,7 @@ def check_prefix(payload: PrefixCheckRequest, db: Session = Depends(get_db), use
except HTTPException:
raise
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Prefix-Prüfung fehlgeschlagen: {exc}") from exc
raise HTTPException(status_code=500, detail=f"Prefix check failed: {exc}") from exc



Expand All @@ -64,7 +64,7 @@ def check_bgp_visibility(payload: BgpVisibilityCheckRequest, db: Session = Depen
except HTTPException:
raise
except Exception as exc:
raise HTTPException(status_code=500, detail=f"BGP-Visibility-Prüfung fehlgeschlagen: {exc}") from exc
raise HTTPException(status_code=500, detail=f"BGP visibility check failed: {exc}") from exc

@router.post('/roa-preflight', response_model=CheckResponse)
def check_roa_preflight(payload: RoaPreflightCheckRequest, db: Session = Depends(get_db), user=Depends(require_operator_or_admin)) -> CheckResponse:
Expand All @@ -76,7 +76,7 @@ def check_roa_preflight(payload: RoaPreflightCheckRequest, db: Session = Depends
except HTTPException:
raise
except Exception as exc:
raise HTTPException(status_code=500, detail=f"ROA-Preflight-Prüfung fehlgeschlagen: {exc}") from exc
raise HTTPException(status_code=500, detail=f"ROA preflight check failed: {exc}") from exc


@router.post('/preflight', response_model=CheckResponse)
Expand All @@ -87,7 +87,7 @@ def check_preflight(payload: PreflightCheckRequest, db: Session = Depends(get_db
except HTTPException:
raise
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Preflight-Prüfung fehlgeschlagen: {exc}") from exc
raise HTTPException(status_code=500, detail=f"Preflight check failed: {exc}") from exc


def _store_and_respond(db: Session, ctype: str, resource: str, origin_as: str | None, result: dict, user_id: int | None = None, change_case_id: int | None = None) -> CheckResponse:
Expand Down
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.1-rc',
'version': 'v0.9.2-rc',
'demo_mode': settings.demo_mode,
'read_only': True,
'data_sources': ['RIPEstat', 'RIPEstat Whois/Registry'],
Expand Down
10 changes: 5 additions & 5 deletions backend/app/core/prefix_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def evaluate_prefix_overall(
}

if not origin_as:
return _warning("Keine vollständige Prefix-Origin-Prüfung möglich", "Ohne Origin-AS ist keine vollständige kombinierte Bewertung von RPKI, Registry/IRR und Routing Visibility möglich.")
return _warning("No complete prefix-origin check possible", "Without an origin AS, a complete combined assessment of RPKI, registry/IRR, and routing visibility is not possible.")

if CheckStatus.CRITICAL in statuses.values():
if statuses["routing"] == CheckStatus.CRITICAL:
Expand All @@ -26,7 +26,7 @@ def evaluate_prefix_overall(
return _critical("Registry/IRR-Origin widerspricht dem angegebenen Origin-AS.", "Ein gefundenes route/route6-Origin weicht vom geprüften Origin-AS ab.")

if statuses["rpki"] == statuses["registry"] == statuses["routing"] == CheckStatus.UNKNOWN:
return _unknown("Keine belastbare Gesamtbewertung möglich.", "RPKI, Registry/IRR und Routing Visibility liefern keine verlässliche Aussage.")
return _unknown("No reliable overall assessment possible.", "RPKI, Registry/IRR und Routing Visibility liefern keine verlässliche Aussage.")

if statuses["rpki"] == CheckStatus.OK and statuses["registry"] == CheckStatus.OK and statuses["routing"] == CheckStatus.OK:
return {
Expand All @@ -38,7 +38,7 @@ def evaluate_prefix_overall(
}

if statuses["rpki"] == CheckStatus.OK and statuses["registry"] == CheckStatus.OK and statuses["routing"] == CheckStatus.UNKNOWN:
return _warning("Routing-Sichtbarkeit konnte nicht belastbar bestimmt werden.", "RPKI und Registry/IRR sind plausibel, aber die Routing-Sichtbarkeit bleibt unklar.")
return _warning("Routing visibility could not be determined reliably.", "RPKI und Registry/IRR sind plausibel, aber die Routing-Sichtbarkeit bleibt unklar.")

if CheckStatus.WARNING in statuses.values():
return _warning("Kombinierte Prefix-Bewertung zeigt Warnhinweise.", "Mindestens eine Einzelprüfung meldet unvollständige oder unsichere Daten.")
Expand All @@ -55,7 +55,7 @@ def _warning(summary: str, explanation: str) -> dict:
"summary": summary,
"explanation": explanation,
"risk": "Die Gesamtaussage bleibt eingeschränkt.",
"recommendations": ["Einzelprüfungen und Rohdaten gezielt nacharbeiten."],
"recommendations": ["Individual checks und Rohdaten gezielt nacharbeiten."],
}


Expand All @@ -64,7 +64,7 @@ def _critical(summary: str, explanation: str) -> dict:
"status": CheckStatus.CRITICAL.value,
"summary": summary,
"explanation": explanation,
"risk": "Erhöhtes Risiko für Fehlrouting, Erreichbarkeitsprobleme oder Sicherheitsvorfälle.",
"risk": "Erhöhtes Risk für Fehlrouting, Erreichbarkeitsprobleme oder Sicherheitsvorfälle.",
"recommendations": ["Abweichung priorisiert prüfen und beheben."],
}

Expand Down
42 changes: 21 additions & 21 deletions backend/app/core/recommendations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

def default_recommendations(status: CheckStatus) -> list[str]:
if status == CheckStatus.OK:
return ["Keine unmittelbare Aktion erforderlich. Monitoring fortsetzen."]
return ["No immediate action required. Continue monitoring."]
if status == CheckStatus.WARNING:
return ["RPKI/Registry-Daten prüfen und Abdeckung verbessern."]
return ["Review RPKI/registry data and improve coverage."]
if status == CheckStatus.CRITICAL:
return ["Origin-AS und ROA sofort verifizieren, da Route verworfen werden kann."]
return ["Erneut prüfen; externe Datenquelle war unzuverlässig oder nicht erreichbar."]
return ["Check again; external data source was unreliable or unavailable."]


def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | None) -> dict:
Expand All @@ -17,11 +17,11 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str |
return {
"status": CheckStatus.WARNING.value,
"summary": "Origin-AS missing",
"explanation": "Für eine vollständige RPKI-Prüfung wird ein Origin-AS benötigt.",
"risk": "Ohne Origin-AS kann nicht geprüft werden, ob eine konkrete Route RPKI-valid wäre.",
"explanation": "A complete RPKI check requires an origin AS.",
"risk": "Without an origin AS, it cannot be verified whether a route would be RPKI valid.",
"recommendations": [
"Ergänze das Origin-AS, zum Beispiel AS3333.",
"Nutze den ASN-Check, um mögliche Origin-AS-Informationen zu finden.",
"Provide the origin AS, for example AS3333.",
"Use the ASN check to find potential origin AS information.",
],
}

Expand All @@ -31,8 +31,8 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str |
"status": CheckStatus.OK.value,
"summary": "RPKI validation successful",
"explanation": "Das Prefix-Origin-Paar ist durch einen passenden ROA abgedeckt.",
"risk": "Kein akutes RPKI-Risiko erkennbar.",
"recommendations": ["Keine akute Maßnahme erforderlich."],
"risk": "No immediate RPKI risk detected.",
"recommendations": ["No urgent action required."],
}
if normalized == "invalid":
return {
Expand All @@ -41,8 +41,8 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str |
"explanation": "Das Prefix wird mit einem Origin-AS geprüft, das nicht durch einen passenden ROA gedeckt ist.",
"risk": "Validierende Netze können diese Route verwerfen. Dadurch kann Erreichbarkeit verloren gehen.",
"recommendations": [
"Prüfe, ob das Origin-AS korrekt ist.",
"Prüfe bestehende ROAs für das Prefix.",
"Check whether the origin AS is correct.",
"Review existing ROAs for the prefix.",
"Erstelle oder korrigiere den ROA nur, wenn du zur Verwaltung dieser Ressourcen berechtigt bist.",
],
}
Expand All @@ -53,8 +53,8 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str |
"explanation": "Für das Prefix existiert ein ROA, aber nicht für dieses Origin-AS.",
"risk": "Validierende Netze können diese Route verwerfen, weil das Origin-AS nicht autorisiert ist.",
"recommendations": [
"Origin-AS prüfen.",
"ROA prüfen.",
"Check the origin AS.",
"Review the ROA.",
"Nur korrigieren, wenn man zur Verwaltung der Ressource berechtigt ist.",
],
}
Expand All @@ -65,9 +65,9 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str |
"explanation": "Für das Prefix existiert ein ROA, aber die angekündigte Prefix-Länge ist länger als die erlaubte maxLength.",
"risk": "Validierende Netze können diese Route verwerfen, obwohl das AS grundsätzlich passen kann.",
"recommendations": [
"Angekündigte Prefix-Länge prüfen.",
"ROA maxLength prüfen.",
"Keine zu breite maxLength setzen, wenn sie nicht notwendig ist.",
"Check announced prefix length.",
"Check ROA maxLength.",
"Do not use an overly broad maxLength unless necessary.",
],
}
if normalized in {"unknown", "not_found", "unknown_roa", "notfound"}:
Expand All @@ -77,19 +77,19 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str |
"explanation": "Für dieses Prefix-Origin-Paar wurde kein passender ROA gefunden.",
"risk": "Das ist nicht automatisch ein Ausfall, schwächt aber die Routing-Sicherheit.",
"recommendations": [
"Prüfen, ob ein ROA angelegt werden sollte.",
"Check whether a ROA should be created.",
"Nur anlegen, wenn man zur Verwaltung berechtigt ist.",
],
}

return {
"status": CheckStatus.UNKNOWN.value,
"summary": "RPKI status could not be determined",
"explanation": "Der RPKI-Status konnte nicht zuverlässig ermittelt werden.",
"risk": "Die Bewertung ist unvollständig.",
"explanation": "The RPKI status could not be determined reliably.",
"risk": "The assessment is incomplete.",
"recommendations": [
"Prüfe die API-Rohdaten.",
"Wiederhole die Prüfung später.",
"Review the API raw data.",
"Repeat the check later.",
"Vergleiche bei Bedarf mit einer zweiten Quelle oder einem lokalen RPKI-Validator.",
],
}
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.1-rc",
"version": "v0.9.2-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.1")
app = FastAPI(title="RouteForge", version="0.9.2")

app.add_middleware(
CORSMiddleware,
Expand Down
Loading
Loading