Supported CMMN elements
Orvanta implements a defined subset of CMMN 1.1. This page says exactly what.
Two different questions, two different tables
Section titled “Two different questions, two different tables”The distinction runs through this whole page and collapsing it is the most common way to over-read a coverage figure:
| Question | Authority |
|---|---|
| Does this parse? Does the model represent it faithfully? | backend/orvanta-cmmn/element-support.json |
| Does this run? Does the engine execute it? | backend/orvanta-cmmn/engine-support.json |
A construct can be in the first and not the second. That combination means “the model is faithful and the construct still does not run” — which is exactly what a byte-identical round trip requires: the editor must carry a processTask through unchanged even though nothing executes it.
Both files are generated from the code, not hand-maintained. Tests in orvanta-cmmn render each table from the types and assert byte-equality, so adding or changing a construct’s support fails that test until the record is updated. If this page and those files disagree, the files are right and this page is stale. The tables below are a rendering of them at platform commit cb4b0e66, 2026-08-10.
What the engine executes
Section titled “What the engine executes”| Construct | Behaviour |
|---|---|
<stage> | Activates its children, completes when they do; honours autoComplete, exit criteria, exitType and exitEventType. |
<planFragment> | No runtime existence — no instance, no state, no lifecycle events. Its plan items are hoisted into the enclosing stage. |
<task> | isBlocking="false" completes on start; isBlocking="true" parks for an external complete. A flowable:type binding is not executed. |
<humanTask> | Parks in active, completes on an external trigger. No form model, no assignee resolution, no task list. |
<milestone> | available → completed via an internal start-plus-occur in one pass; never externally observed in active. |
<userEventListener> | Parks in available, occurs on an external trigger. Honours flowable:availableConditionExpression, including ${cmmn:isStageCompletable()}. |
Sentries: on-parts on complete and occur, @exitCriterionRef narrowing, AND within one sentry, OR across sentries, if-parts over the decidable subset, both trigger modes, @exitEventType (all three values), @exitType (all three values), and an exit criterion on the case plan model terminating the case.
Item control: requiredRule, manualActivationRule, completionNeutralRule, both forms of repetitionRule — counter-based and collection-based — flowable:variableAggregation, and all six parentCompletionRule values.
Lifecycle transitions: every one except fault (no behaviour runs user code, so nothing can raise one), reactivate (case reactivation is refused) and the two async-* transitions (flowable:async is refused). That includes the operator-driven ones — suspend, resume, parentSuspend, parentResume, terminate and close.
Completion: baseline completion, autoComplete, flowable:autoCompleteCondition (decidable subset), and ${cmmn:isStageCompletable()}.
What the engine refuses
Section titled “What the engine refuses”Refusal is a deploy-time error naming the element and the reason — never a silent approximation. This is the deliberate position: a construct that deploys and then quietly misbehaves is worse than one that refuses, because nothing downstream can tell the difference.
| Refused | Why |
|---|---|
<processTask> | Starting a BPMN process as a child job needs the worker and job-queue plumbing. Treating it as a pass-through would complete the case without running the process. |
<caseTask> | Starting a child case needs the same plumbing. |
<decisionTask> | Evaluating a DMN decision needs a dependency the pure engine crate does not carry. |
<timerEventListener> | A timer needs a clock and a re-pull deadline, neither of which exists in a pure engine. A timer that never fires is indistinguishable from a very long one, so the failure would be silent. |
On-parts outside complete / occur | Outside the GA subset — 27 of ~1,005 corpus on-parts. |
caseFileItemOnPart | The Case File model is out of scope. |
| An if-part or rule condition outside the decidable subset | No safe default exists — false strands the sentry, true fires it unconditionally, and both look like a working case. |
flowable:async / flowable:asyncLeave | A transaction boundary with no transaction to bound. |
A computed collectionVariable, or a collection combined with a <condition> or a maxInstanceCount other than unlimited | The plain forms of collection-based repetition execute; these do not. See that page for the exact line. |
flowable:variableAggregation with createOverviewVariable, a class / delegateExpression aggregator, targetExpression / sourceExpression, storeAsTransientVariable, or on a counter-based rule | Plain aggregation executes; each of these is refused for its own reason, listed on the Repetition page. |
| Case reactivation | The reactivate transition’s landing state depends on model shape, and no reactivation driver exists. |
The five stable refusal codes are UnsupportedDefinition, UnsupportedStandardEvent, UndecidableCondition, UnimplementedAttribute and OutOfScopeSubtree.
Refused at parse, before the engine sees them
Section titled “Refused at parse, before the engine sees them”The generic <eventListener> — CMMN’s listener element, which Flowable discriminates with flowable:eventType — is recognised by tag and refused by name. At 78 corpus files (8.1%) it is the single largest thing standing between the parser and the whole corpus.
Also recognised-and-refused, all with zero corpus occurrences as element names: <casePageTask>, <serviceTask>, <httpServiceTask>, <scriptServiceTask>, <sendEventServiceTask>, <externalWorkerServiceTask>, <variableEventListener>, <reactivateEventListener>, <signalEventListener>, <genericEventListener>, <intentEventListener>.
Several of those appear in Flowable’s ~48-entry CMMN palette. Palette breadth is not corpus weight — httpServiceTask, scriptServiceTask, sendEventServiceTask and externalWorkerServiceTask appear zero times across 968 real process definitions.
Explicitly out of scope
Section titled “Explicitly out of scope”- The Case File model —
<caseFile>,<caseFileItem>,<caseFileItemDefinition>,<caseFileItemOnPart>,<caseFileItemStartTrigger>,<caseFileModel>. See Case data for the evidence. - Discretionary planning —
<planningTable>,<discretionaryItem>,<applicabilityRule>. Zero corpus usage. <caseRoles>— the one unmodelled subtree that only warns rather than refusing, because roles reach execution solely throughhumanTask/@performerRefanduserEventListener/@authorizedRoleRefs, both of which are captured.
The conformance scorecard
Section titled “The conformance scorecard”Measured over the 968 CMMN documents in the Flowable OSS repository (flowable/flowable-engine@b0ea691), in-tree as a test fixture. 967 are the acceptance set — one is a deliberate negative fixture.
At platform commit cb4b0e66, 2026-08-10, from the committed ratchet baselines (flowable_corpus/BASELINE.json and flowable_corpus/ENGINE_BASELINE.json):
| Tier | Meaning | Result | |
|---|---|---|---|
parse_ok | The parser returned a model | 967 / 967 | 100% |
| T0 | Parses, with no unsupported element or subtree | 889 / 967 | 91.9% |
| T1 | T0 and the validator returns Ok | 859 / 967 | 88.8% |
| T2 | T1 and every construct is one the engine executes | 667 / 967 | 69.0% |
| T3 | T2 and the engine ran the case to quiescence | 667 / 967 | 69.0% |
Note also that T3 starts every case with no start variables. A model whose sentries read case variables therefore parks, which is a correct outcome and counts toward T3.
What blocks the remaining documents
Section titled “What blocks the remaining documents”The T2 blockers, counted in files (a file blocked by several reasons appears in several rows, so they do not sum):
| Blocker | Files |
|---|---|
| Undecidable sentry if-part | 107 |
processTask | 48 |
caseTask | 46 |
timerEventListener | 39 |
flowable:async | 33 |
repetitionRule/collectionVariable | 22 |
flowable:variableAggregation | 16 |
decisionTask | 13 |
Undecidable autoCompleteCondition | 3 |
Undecidable repetitionRule/condition | 3 |
Earlier at T0/T1: the generic <eventListener> (78 files), an unsupported standard event (21), a timer with no expression (6), a dangling sentryRef (2), a dangling on-part source (1).
Why this is a ratchet, not a pass/fail gate
Section titled “Why this is a ratchet, not a pass/fail gate”Orvanta’s pull-request CI is compile-only and the backend suite runs nightly, so a pass/fail gate on a number nobody sees would carry no signal. The conformance tests instead score against a committed baseline and fail only on regression — a document that used to reach a tier and no longer does. Each phase’s progress lands as a reviewable diff of that baseline.
Parser behaviour worth knowing
Section titled “Parser behaviour worth knowing”- A parse error aborts the walk, so parse errors are reserved for genuinely malformed XML, an exceeded input budget, a missing
<case>, or a case with no<casePlanModel>. Unknown tags, unsupported constructs and dangling references are recorded on the model and the walk continues — you get every problem in one pass, not the first one. - Attribute lookup is namespace-aware everywhere.
flowable:,activiti:andcamunda:are aliased into one dialect class. Flowable Design (design:, 621 corpus files) is a separate tooling class and can never answer a dialect lookup —design:required,design:repetition,design:manualactivationanddesign:completionneutralshadow real execution attribute names, and treating modeller decoration as engine configuration would silently change behaviour. - Condition and timer expression text is captured verbatim and never evaluated or type-checked by the parser.
<timerExpression>in particular is kept as text and not validated as ISO-8601 — the schema says it is “supposed to be”, and Flowable also accepts a JUEL expression that evaluates to one. - Input budget, deliberately identical to the BPMN parser’s: 8 MiB of XML, 5,000 elements, 32 levels of stage nesting, 64 KiB per text node, 128 captured extension children per node, 20,000 diagram-interchange primitives.
Diagram interchange
Section titled “Diagram interchange”The CMMN DI subtree round-trips: <CMMNDI>, <CMMNDiagram>, <CMMNShape> (with dc:Bounds, isCollapsed, isPlanningTableCollapsed), <CMMNEdge> (with every di:waypoint), <CMMNLabel>, <Size>, <Dimension>.
382 of 968 documents carry an actual <CMMNDI> element — 683 declare the namespace without using it.
<CMMNStyle> and its colour children are ignored (zero corpus occurrences), as is di:extension (vendor hints such as flowable:docker, 92 occurrences).
One corpus reality baked into the model: Flowable Design writes an edge’s source on cmmnElementRef and emits sourceCMMNElementRef zero times, so all three reference attributes are kept separately rather than normalised into one.
See also
Section titled “See also”- Authoring a case model — why the editor’s palette is wider than this table.
- Coming from Flowable — case view, migration and testing.