Native APL and the canonical model
APL documents are not translated into BPMN before execution. AplParser
compiles abada.io/v1 YAML directly into the same canonical executable
graph that BPMN XML produces, so both schemas share the runtime, persistence,
versioning and outbox machinery.
Schema dispatch
Section titled “Schema dispatch”The engine sniffs the deployment source:
- a document whose first meaningful line is
version: abada.io/v1compiles through the native APL parser; - anything starting with
<validates and compiles as canonical BPMN 2.0 XML.
Persisted definitions are recompiled through the same dispatch on restart.
The stored schema is recorded per definition version in
process_definitions.schema_type (BPMN_XML or APL_NATIVE) and surfaced in
deployment/list DTOs as schemaType with definitionFormatVersion
(canonical-1 / apl-native-1).
flowchart LR
APL[APL YAML abada.io/v1] --> Parser[AplParser]
BPMN[BPMN 2.0 XML] --> Detect[Dialect detect + validate]
Detect --> Canon[Canonical executable graph]
Parser --> Canon
Canon --> Persist[(Immutable version + schema type)]
Persist --> Runtime[Transactional runtime]
One canonical model
Section titled “One canonical model”The APL node vocabulary maps 1:1 onto the runtime primitives that BPMN imports produce:
| APL node | Runtime element |
|---|---|
webhook |
Start event |
end |
End event |
human-input |
User task (candidate groups from assignees); with outcomes, a decision that routes like an exclusive choice (approval-gate is a deprecated alias) |
engine-task |
External service task on service |
agent |
External service task on the fixed abada:agent topic |
script |
In-transaction script task (the APL form of an embedded Java delegate) |
decision-table |
Business rule task with native abada:decisionTable semantics |
condition |
Exclusive gateway |
inclusive |
Inclusive gateway |
parallel |
Parallel gateway |
message-catch / timer / signal |
Durable catch-event primitives |
event-gateway |
Event-based gateway with atomic sibling cancellation |
The APL twin of the engine’s kitchen-sink process is proven 1:1 against the
BPMN original by AplKitchenSinkTest (PostgreSQL) and Studio’s
APL↔BPMN round-trip check — the two surfaces really do compile to one model.
Strict validation
Section titled “Strict validation”APL closes and tightens holes relative to XML:
- A cycle is allowed only when the step it returns to declares
loop.max_iterations; any other cycle — or a cycle back to the start node or a parallel or inclusive gateway — is rejected at deployment. Cycle detection is linear in the size of the graph. - Duplicate ids, a second
webhook, a missingmetadata.name, undeclared routing targets, unrecognized node types and invalid bounds fail deployment with index-friendly stable error codes and roll back — nothing partial persists. event-gatewayrequires ≥2 competing children; the first to fire advances the instance and every sibling subscription/timer/token is cancelled in the same transaction.- Every error in every node is reported at once, each with its node id and a
JSON Pointer path. The engine-owned JSON Schema
(
engine/src/main/resources/apl/apl-v1.schema.json, served atGET /api/v1/apl/schema) additionally reports unknown fields and wrong value types — warnings in the 1.1.0 release candidates, errors from 1.1.0. POST /api/v1/apl/validateruns this whole pipeline without persisting anything; Studio uses it instead of keeping its own rules, and generates its APL types from the same schema.
Routes that leave a step
Section titled “Routes that leave a step”Besides next, a waiting step can leave through named routes, all compiled
into the same canonical graph as boundary routes attached to the step:
| APL | Taken when |
|---|---|
on_error (optionally per error code) |
A worker reports an error, or the last attempt fails |
on_timeout: { after, then } |
The step is still running after the duration; its work is cancelled |
on_low_confidence, on_invalid_output |
The engine rejects an agent result |
outcomes.<name>.next |
A reviewer decides that outcome |
loop.on_exhausted |
A loop step is reached once more than max_iterations allows |
Timers behind on_timeout and sla_hours are durable jobs, so a restart never
loses them. A route that a step needs but does not declare becomes an
incident (WORK_FAILED, LOOP_EXHAUSTED) instead of a silent stop. BPMN
timer and error boundary events compile to the same routes. Details and
examples: Loops, routes and review decisions.
Immutable versions
Section titled “Immutable versions”Deploying a process key creates a new immutable version that stores the exact source (YAML or XML), its checksum and schema type. New instances select the latest committed definition; running instances retain their stored deployment ID. Studio deploys the exact saved revision of a project document — parsed cache entries use the immutable deployment ID, so redeployment can never change an active instance’s semantics.
Project documents carry a stable metadata.key unique inside their project
and autosave with optimistic revisions (If-Match); a stale save is rejected
instead of overwriting another editor. Project archive is reversible and
blocks document edits, deployment and new starts while running instances drain
normally.