Skip to content

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:

QuestionAuthority
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.

ConstructBehaviour
<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>availablecompleted 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-basedflowable: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()}.

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.

RefusedWhy
<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 / occurOutside the GA subset — 27 of ~1,005 corpus on-parts.
caseFileItemOnPartThe Case File model is out of scope.
An if-part or rule condition outside the decidable subsetNo safe default exists — false strands the sentry, true fires it unconditionally, and both look like a working case.
flowable:async / flowable:asyncLeaveA transaction boundary with no transaction to bound.
A computed collectionVariable, or a collection combined with a <condition> or a maxInstanceCount other than unlimitedThe 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 rulePlain aggregation executes; each of these is refused for its own reason, listed on the Repetition page.
Case reactivationThe 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 weighthttpServiceTask, scriptServiceTask, sendEventServiceTask and externalWorkerServiceTask appear zero times across 968 real process definitions.

  • 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 through humanTask/@performerRef and userEventListener/@authorizedRoleRefs, both of which are captured.

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):

TierMeaningResult
parse_okThe parser returned a model967 / 967100%
T0Parses, with no unsupported element or subtree889 / 96791.9%
T1T0 and the validator returns Ok859 / 96788.8%
T2T1 and every construct is one the engine executes667 / 96769.0%
T3T2 and the engine ran the case to quiescence667 / 96769.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.

The T2 blockers, counted in files (a file blocked by several reasons appears in several rows, so they do not sum):

BlockerFiles
Undecidable sentry if-part107
processTask48
caseTask46
timerEventListener39
flowable:async33
repetitionRule/collectionVariable22
flowable:variableAggregation16
decisionTask13
Undecidable autoCompleteCondition3
Undecidable repetitionRule/condition3

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.

  • 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: and camunda: 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:manualactivation and design:completionneutral shadow 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.

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.