SkyBrief - Product Architecture

System Overview

Architecture Pattern: Hexagonal (Ports & Adapters)

The system uses Clean Architecture with hexagonal pattern to ensure: - Testability: Domain logic independent of infrastructure - Extensibility: Easy to add NOTAMs later (new adapter) - Compliance: Clear audit boundaries for EASA requirements

┌─────────────────────────────────────────────────────────────┐
│                    ADAPTERS (Infrastructure)                 │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │  HTTP API   │  │  PostgreSQL │  │  External Services  │  │
│  │  (Primary)  │  │  (Database) │  │  (Weather, Email)   │  │
│  └──────┬──────┘  └──────┬──────┘  └──────────┬──────────┘  │
│         │                │                    │             │
└─────────┼────────────────┼────────────────────┼─────────────┘
          │                │                    │
          ▼                ▼                    ▼
┌─────────────────────────────────────────────────────────────┐
│                    PORTS (Interfaces)                        │
│         Repository        │        External Service         │
│         Interfaces        │        Interfaces               │
└───────────────────────────┼─────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────┐
│              APPLICATION LAYER (Use Cases)                   │
│                                                              │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│   │   Generate   │  │   Check      │  │   Predict    │     │
│   │   Briefing   │  │ Compliance   │  │   Weather    │     │
│   └──────────────┘  └──────────────┘  └──────────────┘     │
│                                                              │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│   │   Analyze    │  │   Create     │  │   Send       │     │
│   │   Trends     │  │   Report     │  │   Alerts     │     │
│   └──────────────┘  └──────────────┘  └──────────────┘     │
└─────────────────────────────────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────┐
│                  DOMAIN LAYER (Core Logic)                   │
│                                                              │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│   │    Flight    │  │    Weather   │  │ Compliance   │     │
│   │    Entity    │  │   Reading    │  │    Rules     │     │
│   └──────────────┘  └──────────────┘  └──────────────┘     │
│                                                              │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│   │    Alert     │  │    Route     │  │   Operator   │     │
│   │    Entity    │  │    Entity    │  │   Entity     │     │
│   └──────────────┘  └──────────────┘  └──────────────┘     │
└─────────────────────────────────────────────────────────────┘

Core Components

1. Domain Layer (Business Logic)

Entities:

// Flight represents a planned flight
type Flight struct {
    ID          uuid.UUID
    OperatorID  uuid.UUID
    Callsign    string
    Origin      Airport
    Destination Airport
    ETD         time.Time
    ETA         time.Time
    Aircraft    Aircraft
    Status      FlightStatus
    CreatedAt   time.Time
}

// WeatherReading represents captured weather data
type WeatherReading struct {
    ID        uuid.UUID
    FlightID  uuid.UUID
    Type      WeatherType // METAR, TAF, SIGMET
    Station   string      // ICAO code
    Raw       string      // Original text
    Parsed    WeatherData // Structured data
    Timestamp time.Time
    Source    string      // API source
}

// ComplianceReport represents EASA audit documentation
type ComplianceReport struct {
    ID            uuid.UUID
    FlightID      uuid.UUID
    GeneratedAt   time.Time
    WeatherReviewed []WeatherReview
    Valid         bool
    EASASection   string // e.g., "CAT.OP.MPA.175"
    Document      Document // PDF/JSON
}

Domain Services: - ComplianceValidator: Validates EASA compliance rules - WeatherAnalyzer: Analyzes weather trends and patterns - AlertEngine: Generates predictive alerts

2. Application Layer (Use Cases)

Primary Use Cases:

A. Generate Weather Briefing

type GenerateBriefingRequest struct {
    FlightID uuid.UUID
    UserID   uuid.UUID
}

type GenerateBriefingResponse struct {
    Briefing      WeatherBriefing
    ComplianceDoc ComplianceReport
    Alerts        []Alert
}

B. Check Compliance Status

// Validates all required weather elements are present
// Generates compliance documentation

C. Predict Weather Deterioration

// Uses Fireworks.ai to analyze trends
// Generates alerts: "Conditions deteriorating faster than forecast"

3. Adapter Layer (Infrastructure)

A. Weather Data Adapters

// WeatherSource interface (Port)
type WeatherSource interface {
    FetchMETAR(ctx context.Context, icao string) (*METAR, error)
    FetchTAF(ctx context.Context, icao string) (*TAF, error)
    FetchSIGMET(ctx context.Context, region string) ([]SIGMET, error)
}

// AviationWeatherAdapter (US - FREE)
type AviationWeatherAdapter struct {
    baseURL string
    client  *http.Client
}

// OpenMeteoAdapter (EU - €150/month)
type OpenMeteoAdapter struct {
    apiKey  string
    baseURL string
    client  *http.Client
}

// Future: NOTAMSource interface (v2)
type NOTAMSource interface {
    FetchNOTAMs(ctx context.Context, route Route) ([]NOTAM, error)
}

B. Database Adapter

type FlightRepository interface {
    Create(ctx context.Context, flight *Flight) error
    GetByID(ctx context.Context, id uuid.UUID) (*Flight, error)
    ListByOperator(ctx context.Context, operatorID uuid.UUID) ([]Flight, error)
}

type ComplianceRepository interface {
    SaveReport(ctx context.Context, report *ComplianceReport) error
    GetReportByFlight(ctx context.Context, flightID uuid.UUID) (*ComplianceReport, error)
    ListByDateRange(ctx context.Context, operatorID uuid.UUID, start, end time.Time) ([]ComplianceReport, error)
}

C. Intelligence Adapter (Fireworks.ai)

type IntelligenceService interface {
    AnalyzeTrend(ctx context.Context, readings []WeatherReading) (*TrendAnalysis, error)
    GenerateAlert(ctx context.Context, analysis TrendAnalysis) (*Alert, error)
}

type FireworksAdapter struct {
    apiKey  string
    model   string // e.g., "accounts/fireworks/models/mixtral-8x7b"
    client  *http.Client
}

Technology Stack

Backend

Component Technology Rationale
Language Go 1.21+ Your expertise, single binary, fast
Framework Echo or Fiber Lightweight, fast, good middleware support
Architecture Hexagonal/Clean Testable, extensible, compliance-friendly
Database PostgreSQL 15+ ACID compliance (audit trails), PostGIS for spatial
Cache Redis (optional v1.1) Session + weather data caching
Queue PostgreSQL (initially) Background jobs, simple to start
PDF Generation Gotenberg (Docker) Open source, API-based, EU-hosted
Email SendGrid/Postmark Reliable delivery

Frontend

Component Technology Rationale
Framework React 18+ Component-based, ecosystem
Language TypeScript Type safety
Styling Tailwind CSS Fast development
State React Query + Zustand Server + client state management
Maps Leaflet + OpenStreetMap Free, no API keys needed

Infrastructure

Component Technology Rationale
Hosting Hetzner Cloud (Germany) EU data residency, cost-effective, you know it
Server CX42 (4 vCPU, 16GB RAM) €20.76/month, room to grow
Storage Block storage + S3 (Hetzner) Reports, backups
Database Self-hosted PostgreSQL Full control, compliance
Monitoring Grafana + Prometheus Open source, EU-hosted
CI/CD GitHub Actions Free for public repos
DNS Cloudflare CDN + security

Data Sources

Data Source Cost Adapter
METAR/TAF AviationWeather.gov FREE AviationWeatherAdapter
EU Forecasts Open-Meteo Professional €150/month OpenMeteoAdapter
Airspace OpenAIP FREE OpenAIPAdapter
Airports OurAirports FREE OurAirportsAdapter (static import)
NOTAMs (v2) EUROCONTROL EAD €100-300/mo NOTAMAdapter (future)
Intelligence Fireworks.ai ~$5/month FireworksAdapter

Database Schema

Core Tables

-- Operators (Part-CAT, Part-NCC)
CREATE TABLE operators (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    country VARCHAR(2) NOT NULL, -- ISO code
    operator_type VARCHAR(50) NOT NULL, -- Part-CAT, Part-NCC
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Aircraft
CREATE TABLE aircraft (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    operator_id UUID REFERENCES operators(id),
    registration VARCHAR(20) NOT NULL,
    type VARCHAR(100) NOT NULL, -- e.g., "Cessna Citation X"
    icao_type VARCHAR(10), -- e.g., "C56X"
    home_base VARCHAR(4) -- ICAO airport code
);

-- Flights
CREATE TABLE flights (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    operator_id UUID REFERENCES operators(id),
    aircraft_id UUID REFERENCES aircraft(id),
    callsign VARCHAR(20),
    origin VARCHAR(4) NOT NULL, -- ICAO
    destination VARCHAR(4) NOT NULL, -- ICAO
    etd TIMESTAMP NOT NULL, -- Estimated Time of Departure
    eta TIMESTAMP, -- Estimated Time of Arrival
    status VARCHAR(50) DEFAULT 'planned',
    created_by UUID NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Weather Readings (compliance audit trail)
CREATE TABLE weather_readings (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    flight_id UUID REFERENCES flights(id),
    reading_type VARCHAR(20) NOT NULL, -- METAR, TAF, SIGMET
    station VARCHAR(4) NOT NULL, -- ICAO
    raw_text TEXT NOT NULL, -- Original format
    parsed_data JSONB, -- Structured data
    timestamp TIMESTAMP NOT NULL,
    source VARCHAR(100) NOT NULL, -- Which adapter fetched this
    fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Compliance Reports (EASA audit documentation)
CREATE TABLE compliance_reports (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    flight_id UUID REFERENCES flights(id),
    generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    generated_by UUID NOT NULL,
    easa_regulation VARCHAR(50) NOT NULL, -- e.g., "CAT.OP.MPA.175"
    is_compliant BOOLEAN NOT NULL,
    compliance_data JSONB, -- Details of what was checked
    document_path VARCHAR(500), -- Path to PDF
    retention_until TIMESTAMP -- 12 months per EASA
);

-- Alerts (predictive intelligence)
CREATE TABLE alerts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    flight_id UUID REFERENCES flights(id),
    alert_type VARCHAR(50) NOT NULL, -- DETERIORATION, BELOW_MINIMA, etc.
    severity VARCHAR(20) NOT NULL, -- LOW, MEDIUM, HIGH, CRITICAL
    message TEXT NOT NULL,
    recommendation TEXT,
    triggered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    acknowledged_at TIMESTAMP,
    acknowledged_by UUID
);

-- Users (operators, dispatchers, chief pilots)
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    operator_id UUID REFERENCES operators(id),
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    role VARCHAR(50) NOT NULL, -- admin, dispatcher, pilot, readonly
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Future Tables (v2 - NOTAMs)

-- NOTAMs (placeholder for future implementation)
CREATE TABLE notams (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    notam_id VARCHAR(50) UNIQUE NOT NULL,
    location VARCHAR(4) NOT NULL,
    notam_type VARCHAR(20) NOT NULL, -- NEW, REPLACE, CANCEL
    subject VARCHAR(100),
    condition TEXT,
    valid_from TIMESTAMP,
    valid_until TIMESTAMP,
    raw_text TEXT
);

-- Flight-NOTAM associations
CREATE TABLE flight_notams (
    flight_id UUID REFERENCES flights(id),
    notam_id UUID REFERENCES notams(id),
    is_relevant BOOLEAN DEFAULT true,
    PRIMARY KEY (flight_id, notam_id)
);

Hexagonal Architecture Implementation

Directory Structure

/
├── cmd/
│   └── api/                    # Application entry point
│       └── main.go
├── internal/
│   ├── domain/                 # Domain layer (entities, business rules)
│   │   ├── flight.go
│   │   ├── weather.go
│   │   ├── compliance.go
│   │   └── alert.go
│   ├── application/            # Application layer (use cases)
│   │   ├── briefing/
│   │   │   ├── generate.go
│   │   │   └── service.go
│   │   ├── compliance/
│   │   │   └── check.go
│   │   └── intelligence/
│   │       └── predict.go
│   ├── ports/                  # Interface definitions
│   │   ├── weather.go          # WeatherSource interface
│   │   ├── repository.go       // Repository interfaces
│   │   └── intelligence.go     // IntelligenceService interface
│   └── adapters/               # Infrastructure implementations
│       ├── http/
│       │   ├── handlers/
│       │   └── middleware/
│       ├── persistence/
│       │   ├── postgres/
│       │   │   ├── flight_repo.go
│       │   │   └── compliance_repo.go
│       │   └── redis/
│       ├── weather/
│       │   ├── aviationweather.go
│       │   ├── openmeteo.go
│       │   └── composite.go    // Aggregates multiple sources
│       └── intelligence/
│           └── fireworks.go
├── pkg/                        # Shared utilities
│   ├── pdf/
│   └── validation/
├── migrations/                 # Database migrations
└── configs/                    # Configuration files

Example: Adding NOTAMs Later (v2)

When adding NOTAM support, you only need to:

  1. Create the port interface:

    // internal/ports/notam.go
    type NOTAMSource interface {
        FetchNOTAMs(ctx context.Context, route Route) ([]NOTAM, error)
    }

  2. Implement the adapter:

    // internal/adapters/notam/eurocontrol.go
    type EurocontrolAdapter struct {
        // Implementation
    }
    
    func (a *EurocontrolAdapter) FetchNOTAMs(...) ([]NOTAM, error) {
        // Call EUROCONTROL EAD API
    }

  3. Update the composite briefing service:

    // Just add NOTAMSource to the list of sources
    // No changes to domain or application layers

The domain logic for compliance remains unchanged.


Authentication & Authorization

Provider: Clerk (Cloud OIDC)

Why Clerk: - FREE tier: 50,000 monthly retained users, 100 organizations, 20 members per org - B2B features: Built-in organization management, roles, permissions - Time to implement: 30 minutes vs 2-3 days for self-hosted - Cost: FREE until 100 operators × 20 users = 2,000 users - EU compliance: Data Privacy Framework (DPF) certified, GDPR compliant

Alternative considered: Auth0 (25,000 users free, but B2B org features limited)

Role Structure

// Organization Roles (Clerk built-in + custom)
const (
    RoleAdmin       = "admin"       // Chief pilot, ops manager
    RoleDispatcher  = "dispatcher"  // Creates flights, generates briefings
    RolePilot       = "pilot"       // Views briefings, acknowledges alerts
    RoleReadOnly    = "readonly"    // Auditors, trainees
)

Permissions Matrix:

Feature Admin Dispatcher Pilot ReadOnly
Create flights
Generate briefings
View briefings
Download compliance PDF
Acknowledge alerts
Manage aircraft
Manage users
View billing
API access

Architecture

┌─────────────────────────────────────────┐
│         CLERK (Cloud OIDC)              │
│                                         │
│  ┌──────────────┐  ┌──────────────┐    │
│  │  Organization│  │  User Mgmt   │    │
│  │  (Operator)  │  │  + Roles     │    │
│  └──────────────┘  └──────────────┘    │
│                                         │
│  ┌──────────────┐  ┌──────────────┐    │
│  │  JWT Tokens  │  │  Sessions    │    │
│  │  (Custom     │  │  (7-day free │    │
│  │   claims)    │  │   lifetime)  │    │
│  └──────────────┘  └──────────────┘    │
└─────────────────┬───────────────────────┘
                  │
                  ▼ JWT + Org ID
┌─────────────────────────────────────────┐
│         YOUR GO BACKEND                 │
│                                         │
│  ┌──────────────┐  ┌──────────────┐    │
│  │  Auth        │  │  Role        │    │
│  │  Middleware  │  │  Middleware  │    │
│  │  (verify     │  │  (check      │    │
│  │   JWT)       │  │   perms)     │    │
│  └──────────────┘  └──────────────┘    │
│                                         │
│  ┌──────────────┐                      │
│  │  Org Context │  Injects org_id      │
│  │  (tenancy)   │  into requests       │
│  └──────────────┘                      │
└─────────────────────────────────────────┘

Backend Implementation (Go)

JWT Verification Middleware:

// internal/adapters/http/middleware/auth.go

type ClerkClaims struct {
    UserID       string                 `json:"sub"`
    OrgID        string                 `json:"org_id"`        // Custom claim
    Role         string                 `json:"org_role"`      // Custom claim
    Permissions  []string               `json:"org_permissions"`
    Metadata     map[string]interface{} `json:"public_metadata"`
}

func ClerkAuthMiddleware(clerkSecret string) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            token := extractBearerToken(c)

            claims, err := verifyClerkJWT(token, clerkSecret)
            if err != nil {
                return echo.NewHTTPError(401, "Invalid token")
            }

            // Add to context
            c.Set("user_id", claims.UserID)
            c.Set("org_id", claims.OrgID)
            c.Set("role", claims.Role)
            c.Set("permissions", claims.Permissions)

            return next(c)
        }
    }
}

Role-Based Access Middleware:

// internal/adapters/http/middleware/rbac.go

func RequireRole(allowedRoles ...string) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            userRole := c.Get("role").(string)

            for _, role := range allowedRoles {
                if role == userRole {
                    return next(c)
                }
            }

            return echo.NewHTTPError(403, "Insufficient permissions")
        }
    }
}

// Usage:
// e.POST("/flights", createFlight, RequireRole("admin", "dispatcher"))

Organization Context (Multi-tenancy):

// Every request is scoped to an organization
// Database queries automatically filter by org_id

func GetFlights(ctx context.Context, orgID uuid.UUID) ([]Flight, error) {
    query := `
        SELECT * FROM flights 
        WHERE operator_id = $1 
        ORDER BY created_at DESC
    `
    // orgID comes from JWT context
    return db.Query(ctx, query, orgID)
}

Frontend Implementation (React)

Setup:

npm install @clerk/clerk-react

Configuration:

// src/main.tsx
import { ClerkProvider } from '@clerk/clerk-react';

const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;

ReactDOM.createRoot(document.getElementById('root')!).render(
  <ClerkProvider publishableKey={PUBLISHABLE_KEY}>
    <App />
  </ClerkProvider>
);

Protected Routes:

// src/components/ProtectedRoute.tsx
import { useAuth, useOrganization } from '@clerk/clerk-react';
import { Navigate } from 'react-router-dom';

export function ProtectedRoute({ 
  children, 
  allowedRoles 
}: { 
  children: React.ReactNode;
  allowedRoles?: string[];
}) {
  const { isLoaded, userId } = useAuth();
  const { organization, membership } = useOrganization();

  if (!isLoaded) return <Loading />;
  if (!userId) return <Navigate to="/sign-in" />;
  if (!organization) return <Navigate to="/select-org" />;

  if (allowedRoles && !allowedRoles.includes(membership?.role)) {
    return <Navigate to="/unauthorized" />;
  }

  return <>{children}</>;
}

Custom JWT Claims (Clerk Dashboard)

In Clerk Dashboard → JWT Templates:

{
  "org_id": "{{org.id}}",
  "org_role": "{{org.role}}",
  "org_permissions": "{{org.permissions}}",
  "operator_metadata": {
    "plan": "{{org.public_metadata.plan}}",
    "max_aircraft": "{{org.public_metadata.max_aircraft}}"
  }
}

Security Features

Clerk Security (Free Tier): - ✅ Brute force protection - ✅ Bot detection - ✅ Breached password detection - ✅ Session management - ✅ 2FA/MFA (available in Pro)

Implementation Security: - ✅ Always verify JWT on backend - ✅ HTTPS only - ✅ Secure, httpOnly cookies - ✅ CSRF protection - ✅ Audit logging (who did what when)

Cost Analysis

Scenario Users Cost
MVP (Month 1-6) 50 users across 10 operators FREE
Growth (Month 6-12) 200 users across 40 operators FREE
Scale (Year 2) 500 users across 100 operators FREE
Beyond 2,000+ users $20/month (Clerk Pro)

API Design

REST Endpoints

# Authentication (handled by Clerk, endpoints for reference)
POST   /api/v1/auth/login          # Clerk hosted UI
POST   /api/v1/auth/refresh        # Automatic via Clerk SDK
POST   /api/v1/auth/logout         # Clerk SDK

# Flights
GET    /api/v1/flights                    # List flights
POST   /api/v1/flights                    # Create flight
GET    /api/v1/flights/{id}               # Get flight details
PUT    /api/v1/flights/{id}               # Update flight
DELETE /api/v1/flights/{id}               # Delete flight

# Weather Briefings
POST   /api/v1/flights/{id}/briefing      # Generate briefing
GET    /api/v1/flights/{id}/briefing      # Get latest briefing
GET    /api/v1/briefings/{briefingId}/pdf # Download PDF

# Compliance
GET    /api/v1/flights/{id}/compliance    # Check compliance status
GET    /api/v1/compliance/reports         # List compliance reports
GET    /api/v1/compliance/export          # Export for audit

# Intelligence
GET    /api/v1/flights/{id}/alerts        # Get active alerts
POST   /api/v1/alerts/{id}/acknowledge    # Acknowledge alert
GET    /api/v1/alerts/history             # Alert history

# Airports (reference data)
GET    /api/v1/airports                   # Search airports
GET    /api/v1/airports/{icao}            # Get airport details
GET    /api/v1/airports/{icao}/weather    # Current weather

Security & Compliance

GDPR (EU Data Protection)

Requirement Implementation
Data residency Hetzner Germany (EU servers)
Encryption in transit TLS 1.3
Encryption at rest PostgreSQL encryption, file system encryption
Access control JWT tokens, role-based access
Audit logging All actions logged with user + timestamp
Right to erasure API endpoint + automated process
Data minimization Only collect required fields

EASA Compliance Features

  • Immutable audit trail: All weather readings timestamped and signed
  • Retention management: Automatic 12-month retention per ORO.MLR.100
  • Data integrity: Checksums on compliance reports
  • Access logs: Who accessed what weather data when

Deployment Architecture

Single Server Setup (MVP)

Hetzner CX42 (Germany)
├── Docker Compose
│   ├── app (Go binary)
│   ├── postgres (PostgreSQL 15)
│   ├── gotenberg (PDF generation)
│   └── redis (optional)
├── Nginx (reverse proxy + SSL)
└── Let's Encrypt (auto SSL renewal)

Scaling Path

Phase Users Infrastructure Cost
MVP 1-10 Single CX42 €30/month
Growth 10-50 CX42 + managed DB €100/month
Scale 50-200 Load balancer + 2x app servers €300/month

Monitoring & Observability

Metrics to Track

Business Metrics: - Briefings generated per day - Compliance reports created - Alerts triggered and acknowledged - Time saved per briefing (user-reported)

Technical Metrics: - API response times (p50, p95, p99) - Weather API latency and error rates - Database query performance - PDF generation time

Alerts: - Weather API failures - High error rates - Database connection issues - Disk space warnings

Tools

  • Metrics: Prometheus + Grafana
  • Logging: Structured JSON logs (Go slog)
  • Error tracking: Sentry
  • Uptime: UptimeRobot or Grafana Cloud


Document Version: 2.0
Last Updated: March 2026
Next Review: After Week 4 validation gate

results matching ""

    No results matching ""