Skip to content

Architecture

Floh is a multi-step workflow orchestration platform. Internal administrators design and manage workflows through an admin UI, while external users (invitees, approvers, task assignees) interact through a separate public portal. The platform integrates with OIDC identity providers, SMTP servers, and arbitrary external services via a pluggable connector system.

System Context

graph LR
    Admin["Admin User"]
    External["External User"]
    IdP["OIDC Provider"]
    SMTP["SMTP Server"]
    ExtSvc["External Services"]
    AI["AI Tools (MCP)"]

    Admin --> AdminUI["Admin UI"]
    External --> PortalUI["Portal UI"]
    AI --> MCP["MCP Server"]

    AdminUI --> Server["API Server"]
    PortalUI --> BFF["Portal BFF"]
    BFF --> Server
    MCP --> Server

    Server --> DB["PostgreSQL / MySQL"]
    Server --> Redis["Redis"]
    Server --> IdP
    Server --> SMTP
    Server --> ExtSvc

Packages

Package Name Role
packages/server @floh/server Fastify REST API, workflow engine, BullMQ worker
packages/web @floh/web Angular admin UI with workflow designer, reports, and full management
packages/portal-bff @floh/portal-bff Stateless Fastify proxy that whitelists routes for portal users
packages/portal-web @floh/portal-web Minimal Angular UI for external users (tasks, approvals, invitations)
packages/shared @floh/shared TypeScript types and constants shared across packages
packages/mcp @floh/mcp Model Context Protocol server exposing Floh to AI tools

Dependency Graph

graph TD
    shared["@floh/shared"]
    server["@floh/server"]
    web["@floh/web"]
    portalBff["@floh/portal-bff"]
    portalWeb["@floh/portal-web"]
    mcp["@floh/mcp"]

    server --> shared
    web --> shared
    portalWeb --> shared
    portalBff -.->|"HTTP proxy"| server
    mcp -.->|"HTTP client"| server

Solid arrows are compile-time workspace:* dependencies. Dashed arrows are runtime HTTP connections.

Request Flows

Admin Path

sequenceDiagram
    participant Browser
    participant Web as Admin UI (nginx)
    participant Server as API Server
    participant DB as PostgreSQL
    participant Redis

    Browser->>Web: GET /
    Web-->>Browser: SPA assets
    Browser->>Server: /api/* (cookie auth)
    Server->>DB: Query
    Server->>Redis: Session / queue
    Server-->>Browser: JSON response

Portal Path

sequenceDiagram
    participant Browser
    participant PortalWeb as Portal UI (nginx)
    participant BFF as Portal BFF
    participant Server as API Server

    Browser->>PortalWeb: GET /
    PortalWeb-->>Browser: SPA assets
    Browser->>BFF: /api/* (cookie auth)
    Note over BFF: Route whitelist check
    Note over BFF: Strip scope=all
    BFF->>Server: Forward with X-Portal-Origin
    Server-->>BFF: JSON response
    BFF-->>Browser: Passthrough

The BFF has no database, Redis, or OIDC connections. It only forwards whitelisted routes and enforces scope so portal users see only their own tasks and approvals. See Portal.

Server Internals

Plugin Chain

Fastify plugins are registered in packages/server/src/app.ts:

  1. CORS — origin whitelist with credentials
  2. Multipart — file uploads
  3. Cookie — session cookies
  4. Rate Limit — 200 req/min default
  5. Swagger — OpenAPI docs at /api/docs
  6. CSRF — double-submit cookie (when OIDC enabled)

Decorators attach shared instances (db, redis, config, logService, schedulerService, escalationService) to the Fastify app instance.

Module Organization

Each domain lives under packages/server/src/modules/:

Module Responsibility
auth OIDC login/callback, sessions, JWT, guards, API tokens
workflows Definition CRUD, engine, step executor, graph walker, lifecycle
tasks Step/task management for running workflows
approvals Approval routing, decisions, escalation
connectors Registry, execution dispatch, OAS parser, script sandbox
scheduler BullMQ queue, cron triggers, delayed jobs
notifications Email (Handlebars templates) and in-app notifications
roles Role definitions, entitlements, assignments
audit Immutable audit log, checkpoints
reports Report templates, saved reports, scheduled delivery
documents Document templates and submissions
organizations Multi-org support and memberships
escalation Reminder and reassignment logic
health Health check endpoint

Workflow Engine

The engine (modules/workflows/engine.ts) executes runs synchronously with a Redis distributed lock per run:

  1. Acquire lock floh:run-lock:{runId}
  2. Load run and definition, build step graph
  3. Walk steps via graph transitions (max 1000 steps per pass)
  4. Delegate to StepExecutor by step type: action, condition, connector, approval, notification, consent, document submission, role grant/revoke, fork, join, sub-workflow
  5. Steps that require external input (approval, consent, document submission) return a waiting_* status and pause the run
  6. When external input arrives, the engine resumes from the waiting step
  7. Fork/join steps enable parallel branches with barrier synchronization

See System Architecture and Fork/Join Parallel Branches.

Background Jobs

A single BullMQ queue (workflow-scheduler) handles all background work:

Job Schedule Purpose
trigger-workflow Per-schedule cron Start workflow runs on schedule
escalation-reminder Delayed Send approval reminder notifications
escalation-reassignment Delayed Reassign overdue approvals
role-expiry-check Hourly Revoke expired role assignments
document-expiry-check Hourly Flag expired documents
entitlement-reconciliation Daily 2:00 UTC Reconcile all entitlements
stuck-run-recovery Every 15 min Recover runs stuck beyond timeout
run-orphan-cleanup Daily 2:30 UTC Clean up orphaned run artifacts
audit-checkpoint Every 6h (configurable) Create audit integrity checkpoint
deliver-scheduled-report Per-report cron Generate and deliver reports
connector-resource-sync Per-connector cron Sync external resources

The worker can run in-process (default, for development) or as a separate process (WORKER_MODE=separate, recommended for production). See Service Architecture.

Data Layer

  • ORM: Kysely (type-safe query builder, no code generation)
  • Databases: PostgreSQL 16 (primary) or MySQL 8
  • Migrations: SQL files wrapped in TypeScript up/down functions (packages/server/src/db/migrations/)
  • Repositories: Each module has a repository that wraps Kysely, handles snake_case/camelCase conversion, and serializes/deserializes JSON columns
  • Soft deletes: deleted_at column with a far-future sentinel value for index efficiency (see Decision Records)
  • Flexible data: Workflow definitions, variables, and connector configs are stored as serialized JSON in TEXT columns
  • Encryption at rest: Connector secrets and session data encrypted with AES-256-GCM using rotatable keys

See Security and Encryption Keys.

Authentication and Authorization

  1. OIDC flow: Server-side authorization code flow — login redirects to the IdP, callback exchanges the code, session is created in Redis
  2. Sessions: Encrypted, stored in Redis with 24h TTL, refreshed on access
  3. CSRF: Double-submit cookie pattern for mutating requests when OIDC is enabled
  4. API tokens: floh_-prefixed tokens for programmatic access (dev/test)
  5. RBAC: Permission-based route guards (requireRole, requirePermission) with role-to-permission mappings
  6. Dev bypass: When OIDC_ISSUER is unset, a dev user with admin role is used

See Security and Roles & Entitlements.

Deployment

graph TD
    Internet["Internet"]
    Caddy["Caddy (TLS)"]
    AdminUI["web (nginx:8080)"]
    PortalUI["portal-web (nginx:8080)"]
    BFF["portal-bff (:3001)"]
    Server["server (:3000)"]
    Worker["worker"]
    PG["PostgreSQL"]
    Redis["Redis"]

    Internet --> Caddy
    Caddy -->|"domain/api/*"| Server
    Caddy -->|"domain/*"| AdminUI
    Caddy -->|"portal-domain/api/*"| BFF
    Caddy -->|"portal-domain/*"| PortalUI
    BFF --> Server
    Server --> PG
    Server --> Redis
    Worker --> PG
    Worker --> Redis
  • TLS termination: Caddy with automatic Let's Encrypt certificates
  • Container images: Multi-stage Docker builds, pushed to GHCR with semver + SHA + latest tags
  • Deployment target: Single host via Docker Compose (see Deployment)
  • CI/CD: GitHub Actions — changeset check on PR, automated version PRs on merge, deploy on release

See Deployment and Worker Deployment.

Further Reading

Topic Document
Detailed system architecture System Architecture
Service boundaries and scaling Service Architecture
Connector execution models Connector Architecture
Portal architecture Portal
Security model Security
Deployment guide Deployment
Developer quickstart Quick Start
Architecture decisions Decision Records