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:
- CORS — origin whitelist with credentials
- Multipart — file uploads
- Cookie — session cookies
- Rate Limit — 200 req/min default
- Swagger — OpenAPI docs at
/api/docs - 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:
- Acquire lock
floh:run-lock:{runId} - Load run and definition, build step graph
- Walk steps via graph transitions (max 1000 steps per pass)
- Delegate to
StepExecutorby step type: action, condition, connector, approval, notification, consent, document submission, role grant/revoke, fork, join, sub-workflow - Steps that require external input (approval, consent, document submission) return a
waiting_*status and pause the run - When external input arrives, the engine resumes from the waiting step
- 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_atcolumn 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¶
- OIDC flow: Server-side authorization code flow — login redirects to the IdP, callback exchanges the code, session is created in Redis
- Sessions: Encrypted, stored in Redis with 24h TTL, refreshed on access
- CSRF: Double-submit cookie pattern for mutating requests when OIDC is enabled
- API tokens:
floh_-prefixed tokens for programmatic access (dev/test) - RBAC: Permission-based route guards (
requireRole,requirePermission) with role-to-permission mappings - Dev bypass: When
OIDC_ISSUERis 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 |