Element templates¶
operaton-contracts renders one Camunda 7 element template per external task
topic into .operaton/element-templates/, for the Operaton modeler and
compatible bpmn.io modelers.
Configuration¶
[tool.operaton-contracts]
specs = "OperatonTasks:TEMPLATES" # module:attribute, required
icon = "logo.svg" # SVG for specs without their own
reserved-topics = ["legacy.topic"] # topics owned by other workers
schema-url = "https://…?job=release" # pinned schema; has a default
The specs module and icon path are resolved from the project root. When the
robot package already uses OperatonContracts at runtime for task contracts
and Robot validation, put the template specs in OperatonTasks.py alongside
the contract models. This adds no package dependency. The templates extra is needed only for
validate, which checks templates against the upstream schema. With devenv, provide jsonschema through
devenv.nix and keep it out of uv's dev group.
from OperatonContracts.templates import ElementType, TaskTemplate, TemplateGroup
TEMPLATES = (
TaskTemplate(
topic="records.process",
template_id="org.example.records-process", # stable; never reuse
name="Process Records",
description="Process selected records.",
filename="records-process.json",
inputs=ProcessRecordsInput,
outputs=ProcessRecordsOutput,
groups=(TemplateGroup("main", "Processing"),),
input_group="main",
output_group="main",
version=1, # bump when a published template changes
keep_versions=(), # earlier published versions; see Versions
element_type=ElementType.SERVICE_TASK, # see Element types
),
)
Alternatively, keep the specs out of the runtime task module in a root-level
OperatonTemplates.py and set specs = "OperatonTemplates:TEMPLATES". If
using pur wrap, list that module and the root-level icon in .wrapignore
so they stay out of the robot package.
A spec may set icon="other.svg" (relative to the project root) to embed its
own SVG instead of the configured icon. Icons must be files inside the
project root; list per-spec icons in .wrapignore as well.
The default groups are inputs ("Inputs") and outputs ("Results").
input_group and output_group must be ids from groups; use one id for
both to show every property in a single group.
Commands¶
operaton-contracts generate # write templates, remove stale ones
operaton-contracts check # offline; the default command
operaton-contracts validate # against the pinned upstream schema (network)
All commands accept --root <dir> (default: the current directory) and are
also available as python -m OperatonContracts. They exit with status 0 on
success, 1 when checks or schema validation fail, and 2 for usage or
configuration errors (a missing or invalid [tool.operaton-contracts]
table, specs module, or icon), always with a message instead of a traceback.
The generated .operaton/ directory is optional source control state. A robot
package may add .operaton/ to .gitignore; run operaton-contracts generate
to recreate the templates when the modeler needs them. If CI runs check with
the directory ignored, generate the files first: check verifies the on-disk
templates against the contracts and reports missing or stale files. Packages
that use keep_versions must commit .operaton/, because earlier versions
exist only there.
Versions¶
The worker always serves the latest contract, but diagrams keep the template version they were modeled with. To let the modeler resolve those diagrams and offer "Update template", keep earlier published versions in the file:
TaskTemplate(..., version=4, keep_versions=(2, 3))
- Bump
versionwhenever a published template changes; until then, keep regenerating the same version. generaterenders the current version from the contracts and copies each kept version unchanged from the existing file (matched byidandversion), writing a JSON list, newest first. Withoutkeep_versionsthe file holds a single template.versionand kept versions are integers of at least 1;keep_versionsis a tuple (write(1,), not(1)) of unique versions lower thanversion.- Each kept version must appear exactly once in the file; a missing or
duplicated one is an error (restore the file from version control) rather
than silently resolved. If the file is gone but another file holds the
template id, the error suggests renaming it, e.g. after a
filenamechange. checkexplains drift: a committed version absent fromkeep_versions"would be dropped", a committed version newer thanversionasks for a higher version, a changed current version should get a bump if it is already published, and templates with another id in the file would be dropped.- Kept versions are the committed file's content, so
checkcannot detect a hand edit to them; review such diffs like any other change. - Remove a version by dropping it from
keep_versionsand runninggenerate. - Kept versions are copied as they were, so they may differ in anything,
including
appliesTo.
Element types¶
C7/Operaton implements these BPMN elements as external tasks with a topic,
and element_type (from OperatonContracts.templates) picks one per
template:
ElementType |
appliesTo |
elementType |
|---|---|---|
SERVICE_TASK (default) |
bpmn:ServiceTask |
— |
SEND_TASK |
bpmn:SendTask |
— |
BUSINESS_RULE_TASK |
bpmn:BusinessRuleTask |
— |
MESSAGE_INTERMEDIATE_THROW_EVENT |
bpmn:IntermediateThrowEvent |
eventDefinition: bpmn:MessageEventDefinition |
MESSAGE_END_EVENT |
bpmn:EndEvent |
eventDefinition: bpmn:MessageEventDefinition |
For message events, camunda:type and camunda:topic live on the
messageEventDefinition, and elementType makes the modeler add that
definition when the template is applied. This needs the vasara-bpm forked
modeler; upstream C7 element templates ignore eventDefinition. Operaton
rejects output mappings on end events, so MESSAGE_END_EVENT needs an
outputs contract without fields. Changing element_type changes the
template's bindings, so bump version once the template is published.
Generated structure¶
Each template has $schema, name, id, description, version,
appliesTo (and elementType for message events), groups, properties,
and the icon. Properties start with Hidden camunda:type = external and
camunda:topic = <topic>.
Inputs¶
| JSON Schema | Template property |
|---|---|
boolean |
Boolean |
string (any format, e.g. date) |
String |
string with enum (Literal) |
Dropdown with choices |
array of string |
List with itemType: "String" |
array of string with items.enum |
List with choices and display: "taglist" |
object of string (dict[str, str]) |
Map; see below |
| anything else | error, unless a type hint is given |
value: thevaluehint, the schemadefault,[]forList, or"${alias}". The value must fit the property type.constraints.notEmpty:minLength ≥ 1,minItems ≥ 1, or a requiredString.group: the spec'sinput_group, or thegrouphint.
Map inputs¶
A dict[str, str] input renders as a Map, bound as a
camunda:inputParameter holding a camunda:map.
- Fixed keys (
entries): theentrieshint, else the keys ofdict[Literal[...], str](one or more) or ofdict[StrEnum, str]. Hinted keys must be among the contract'sLiteral/Enumkeys and match a key pattern. Without fixed keys the Map getsadditionalEntries: true, so modeler users add their own keys. - Map
constraintsapply to every value: valuemin_length ≥ 1givesnotEmpty, and valuemin_length,max_length, andpatterncarry over. A key pattern (dict[Annotated[str, StringConstraints(pattern=...)], str]) becomeskeyPatternfor user-added keys. Other key constraints are rejected. Being required does not addnotEmpty; to require an entry's value, put"constraints": {"notEmpty": true}in that entry. Literalvalue choices (dict[Literal["a"], Literal["x", "y"]]) make each entry aDropdown; they need fixed keys. Hintedchoicesmust be among those values, and a Dropdown entry'svaluemust be one of its choices.- Keys must be strings: integer
Enumkeys are rejected. - A Map takes no value: use
default_factory=dictor= {}, and pre-fill entries withvaluein theentrieshint. - Entries are checked against the element-template schema's entry rules:
non-empty unique keys, string values (boolean for
Boolean),choicesonly and always onDropdown,placeholderonly onString/Text, andconstraints/optionalonly onString/Text/Dropdown, neveroptionalwithnotEmpty. binding:camunda:inputParameternamed by the alias.
Outputs¶
Each output is a String property bound as a camunda:outputParameter
with source = "${alias}". Its value, the target process variable, is the
alias; modeler users may rename it.
Outputs accept only the value and group hints. value="other" changes
the default target variable. value="" leaves the target empty and adds
"optional": true, so the output mapping is not written unless a modeler
user names a variable; the worker's value is then not mapped to any process
variable. A hinted name may not contain whitespace, $, {, or }, and
two outputs may not map to the same variable.
What check verifies¶
- On-disk templates equal the rendered ones (2-space JSON and a final
newline), and there are no stray
*.jsonfiles. - Spec topics equal the keys of
[tool.purjo.topics]; no topic is reserved; ids, filenames, and topics are unique; every topic setsprocess-variables = false. - Exactly one suite defines each topic's task. Suites are collected
recursively, skipping hidden directories,
tests/,lib/,examples/, andtest_*.robot. Task, keyword, and variable names are matched the way Robot matches them: case, spaces, and underscores are ignored, and${name: type},scope=BPMN:TASK, andLibrary.Keywordforms are accepted. - That suite declares a default for every input, typed for non-string inputs;
the task calls
Validate Task Input <InputContract>; and it sets every output withVAR … scope=${BPMN:TASK}.
Schema¶
The pinned default is the Operaton element-templates schema v0.8.3:
https://gitlab.com/vasara-bpm/vscode-operaton-bpmn-js-modeler/-/jobs/artifacts/v0.8.3/raw/operaton-element-templates-schema-v0.8.3.json?job=release
The ?job=release query is required. The same URL is written as $schema in
every template. Validation uses jsonschema's Draft 7 validator and needs the
templates extra.