Appearance
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:
- 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. - 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-levelform,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.
| Module | Package | Responsibility |
|---|---|---|
| form | capability/form, form | Form definitions, versions, submissions, embed tokens, event rail, submission PDFs |
capability/email | Email capability data layer | |
| emailtemplate | capability/emailtemplate | Bounded email-template documents / assets |
| phone | capability/phone | Vapi.ai inbound/outbound voice-call records and webhooks |
| signature | capability/signature | Native PAdES e-sign ceremonies and signed-document state |
| document | document | Tenant/capability-gated document registry (S3-backed) |
| access | capability/access | Capability access requests and owner approvals |
| secrets | capability/secrets | Encrypted per-tenant secret storage |
| capability | capability/capability | Capability catalog / subscription and tier gating |
| minion | capability/minion | Background worker / helper tasks |
| config | capability/config | Capability-level configuration |
| workflow | workflow | WORKFLOW 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/FormSubmissionOutboxConsumerandFormEventOutbox— 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 anX-Tenant-Idscope; identity/tenant-management endpoints are exempt (TenantFilter,DeploymentTenantFilter). See Multi-Tenancy. - Authorization enabled — the auto-seeded
camunda-admingroup holds full grants; non-admin users need explicit tenant-scoped authorizations.RestApiAuthFilterbinds group memberships into the auth context. - Gateway JWT authentication —
GatewayJwtAuthenticatorverifies short-lived JWTs signed by the consumer gateway against a configured JWKS URL (opt-in vialuke.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
@PostConstructguards refuse to boot (or warn loudly) on insecure configuration (see Deployment). - Correlation IDs & health probes —
CorrelationIdFilterfor request tracing; Spring Boot Actuator liveness/readiness endpoints for Render.
Technology
| Layer | Choice |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.4 (3.4.13) |
| Process engine | FluxNova 2.0.3 (FINOS Camunda-7 fork) |
| Database | PostgreSQL 16 (H2 in local dev) |
| Migrations | Flyway (V1–V13) |
| Connectors | FluxNova Connect (HTTP/SOAP) |
| Scripting | GraalVM JS, Groovy, Jython (JSR-223) |
| Signatures | Apache PDFBox 3 + BouncyCastle (PAdES) |
| Object storage | AWS SDK v2 (S3) for document/signature stores |
| Build | Maven (wrapper ./mvnw) |
| Container | Multi-stage Docker (JDK 21 build → JRE 21 runtime, non-root) |
| Hosting | Render (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:runOn 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:
| Guard | Enforces |
|---|---|
AuthHardeningGuard | No auth layer is left failing open |
InsecureKeyGuard | No dev-default …-change-me secrets/HMAC keys in use |
AdminPasswordGuard | Bootstrap admin password is not the default |
H2ConsoleGuard | H2 console not exposed |
EdgeHardeningGuard | Edge-facing hardening constraints |
DocumentAccessGuard | Document-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
prodprofile not yet enabled — the fail-fast guards ship in code but require the full secret set beforepostgres,prodcan 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-errorby 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.