Skip to content

Core Engine

The Core Engine is Lukeflow's multi-tenant BPMN process engine — a FluxNova (FINOS Camunda-7 fork) runtime wrapped in Spring Boot, extended with an in-process capability data layer that stores and serves the domain data behind forms, email, signatures, phone, documents and more. Partial · deployed

Repository: luke-core-engine · Type: Platform service · Stack: Java 21 / Spring Boot 3.4 / FluxNova

Overview

luke-core-engine is the heart of the Lukeflow platform. It plays two roles in a single deployable:

  1. A BPMN workflow engine — it embeds FluxNova 2.0.3 (the FINOS fork of Camunda 7) and exposes the standard Camunda REST API under /engine-rest/**. Customer organizations map to Camunda tenants; an operator "parent cluster" tenant exists for cluster managers.
  2. A capability data layer — a set of in-process Spring modules (forms, email, signatures, phone, documents, access, secrets, and more) that own the persistent state for each Lukeflow capability. BPMN processes orchestrate these modules and write results back transactionally.

Collapsing the former standalone luke-capability-engine into the process engine was a deliberate architecture decision: one JVM, one database, one transaction boundary. The engine orchestrates and stores, which removes an entire class of cross-service consistency problems.

See Capabilities, Multi-Tenancy, and Authentication & Authorization for the platform-wide concepts this service implements.

At a glance

~51 REST controllers · ~26 services · ~271 endpoint mappings · 13 Flyway migrations (V1–V13) · ~361 tests.

Architecture

The service is a single Spring Boot application (LukeCoreEngineApplication) that boots the FluxNova engine and registers the capability modules alongside it. Two concerns run in the same process and share one PostgreSQL database:

  • The engine tier — FluxNova BPMN runtime, the Camunda REST API, identity/authorization, tenant filters, and connector/scripting plugins.
  • The capability tier — domain modules under com.luke.engine.capability.* (plus top-level form, document, emailasset, workflow) that persist and serve capability data.

Capability modules

Each module owns its own tables (Flyway-managed) and REST surface. Modules are wired in-process — there is no network hop between the engine and a capability.

ModulePackageResponsibility
formcapability/form, formForm definitions, versions, submissions, embed tokens, event rail, submission PDFs
emailcapability/emailEmail capability data layer
emailtemplatecapability/emailtemplateBounded email-template documents / assets
phonecapability/phoneVapi.ai inbound/outbound voice-call records and webhooks
signaturecapability/signatureNative PAdES e-sign ceremonies and signed-document state
documentdocumentTenant/capability-gated document registry (S3-backed)
accesscapability/accessCapability access requests and owner approvals
secretscapability/secretsEncrypted per-tenant secret storage
capabilitycapability/capabilityCapability catalog / subscription and tier gating
minioncapability/minionBackground worker / helper tasks
configcapability/configCapability-level configuration
workflowworkflowWORKFLOW capability tables and integration event rail (hidden behind a launch flag)

Outbox / job-worker write-back

Capabilities that interact with a BPMN process do not call the engine directly from a request thread. Instead they use a transactional outbox pattern: a capability writes its state change and an outbox row in the same DB transaction, then a scheduled consumer drains the outbox and drives the corresponding process instance. Concrete instances include:

  • FormSubmissionOutbox / FormSubmissionOutboxConsumer and FormEventOutbox — form submissions correlate into a waiting process.
  • SignatureProcessOutbox / SignatureProcessOutboxConsumer + SignatureCeremonyWriteBackDelegate — signing ceremonies feed back into the process.
  • PhoneCallProcessOutbox / PhoneCallProcessOutboxConsumer + PhoneCallWriteBackDelegate — call outcomes feed back.
  • IntegrationEventOutbox — workflow integration events.

Why an outbox

The outbox guarantees the capability's data commit and the "notify the process engine" action are atomic. If the engine is briefly unreachable, the state is durably recorded and the consumer retries — no lost submissions, no phantom process instances. Scheduling is enabled via AsyncSchedulingConfig.

Key features

  • Standard Camunda REST API — full /engine-rest/** surface (process definitions, instances, tasks, deployments, identity) served by FluxNova.
  • Multi-tenancy by header — every /engine-rest/* request carries an X-Tenant-Id scope; identity/tenant-management endpoints are exempt (TenantFilter, DeploymentTenantFilter). See Multi-Tenancy.
  • Authorization enabled — the auto-seeded camunda-admin group holds full grants; non-admin users need explicit tenant-scoped authorizations. RestApiAuthFilter binds group memberships into the auth context.
  • Gateway JWT authenticationGatewayJwtAuthenticator verifies short-lived JWTs signed by the consumer gateway against a configured JWKS URL (opt-in via luke.auth.gateway.enabled).
  • In-process capability data layer — forms, email, signatures, phone, documents, access, secrets and more (see table above).
  • Transactional outbox write-back — capabilities drive BPMN processes durably (see above).
  • Connectors & scripting — FluxNova Connect (HTTP/SOAP service tasks) plus JSR-223 scripting engines (GraalVM JS, Groovy, Jython) for BPMN logic.
  • Fail-fast security guards — a set of @PostConstruct guards refuse to boot (or warn loudly) on insecure configuration (see Deployment).
  • Correlation IDs & health probesCorrelationIdFilter for request tracing; Spring Boot Actuator liveness/readiness endpoints for Render.

Technology

LayerChoice
LanguageJava 21
FrameworkSpring Boot 3.4 (3.4.13)
Process engineFluxNova 2.0.3 (FINOS Camunda-7 fork)
DatabasePostgreSQL 16 (H2 in local dev)
MigrationsFlyway (V1–V13)
ConnectorsFluxNova Connect (HTTP/SOAP)
ScriptingGraalVM JS, Groovy, Jython (JSR-223)
SignaturesApache PDFBox 3 + BouncyCastle (PAdES)
Object storageAWS SDK v2 (S3) for document/signature stores
BuildMaven (wrapper ./mvnw)
ContainerMulti-stage Docker (JDK 21 build → JRE 21 runtime, non-root)
HostingRender (Blueprint)

Local development

The engine runs against an embedded H2 store by default — no external services required.

bash
# Run with H2 (default, no setup) — boots on http://localhost:8080
./mvnw spring-boot:run

# Run with PostgreSQL (matches prod)
SPRING_PROFILES_ACTIVE=postgres \
DB_URL=jdbc:postgresql://localhost:5432/luke_camunda \
DB_USERNAME=luke DB_PASSWORD=luke \
./mvnw spring-boot:run

# Compile, run the full test suite, and package (what CI runs)
./mvnw -B -ntp verify

# Wipe and restart the dev DB
lsof -ti tcp:8080 | xargs kill 2>/dev/null
rm -rf luke-data/
./mvnw spring-boot:run

On a fresh database the engine seeds an admin user (password from CAMUNDA_ADMIN_PASSWORD, default admin), the camunda-admin group with full grants, and the parent_cluster operations tenant.

parent_cluster is not a signup default

parent_cluster is the operator/ops tenant used by cluster managers. End-user organizations create their own tenants via the consumer app's signup flow — never reuse the parent cluster as a customer tenant.

Deployment

The service ships as a multi-stage Docker image (JDK 21 build stage → JRE 21 runtime, running as a non-root luke user with a real writable home for GraalVM JS). It is deployed on Render from the render.yaml Blueprint (the canonical platform blueprint lives in luke-platform), which provisions the web service plus a managed PostgreSQL 16 instance and wires the DB connection env vars automatically. Render injects $PORT; the JVM is container-sized with MaxRAMPercentage=55.0 to leave headroom for the scripting engines' off-heap footprint on the starter plan.

Health probes: /actuator/health/readiness (used by Render — goes down during drain / DB loss) and /actuator/health/liveness.

Security profiles

dev/qa run with SPRING_PROFILES_ACTIVE=postgres and default-lenient guards. Production is meant to run postgres,prod, where the dedicated prod profile (StrictProfile) flips the opt-in guards into fail-fast mode:

GuardEnforces
AuthHardeningGuardNo auth layer is left failing open
InsecureKeyGuardNo dev-default …-change-me secrets/HMAC keys in use
AdminPasswordGuardBootstrap admin password is not the default
H2ConsoleGuardH2 console not exposed
EdgeHardeningGuardEdge-facing hardening constraints
DocumentAccessGuardDocument-registry access scoping

Do not add ,prod prematurely

The prod profile intentionally refuses to boot if required secrets (internal shared secret, secrets master key, real embed HMAC, capability-operator credentials, gateway JWKS) are absent. Switch to postgres,prod only after every sync:false secret is set, or the service will (by design) fail to start.

See Deployment Topology for how this service fits the wider fleet.

Status & gaps

The Core Engine is deployed and running on dev/qa, but several items are intentionally incomplete:

  • FluxNova production DB rollout is deferred — the migration from CIBSeven to FluxNova is validated (tests green) but the production database cutover has not been executed; prod still runs the pre-migration engine until the rollout is scheduled.
  • Production prod profile not yet enabled — the fail-fast guards ship in code but require the full secret set before postgres,prod can be turned on. Until then they log warnings rather than blocking boot.
  • Security scans are non-gating — the Semgrep / gitleaks / Trivy workflow runs on every PR but is continue-on-error by design; findings are informational until the baseline is triaged.
  • Some capabilities are data-layer-only — certain modules persist and serve data but their end-to-end runtime (e.g. outbound send paths, WORKFLOW) is behind launch flags or not fully wired yet.

For the platform-wide readiness picture, see the Completeness Scorecard.

Lukeflow Manual · documentation snapshot July 2026