Skip to content

Builder: ElementOptions has no documentation field — <bpmn:documentation> is unreachable from the fluent API #151

Description

@vobu

Summary

ElementOptions has no documentation field, so the fluent builder cannot set
<bpmn:documentation> on any element — even though the rest of the library fully supports it:
bpmn-model.d.ts declares documentation?: string on flow elements, bpmn-parser.js reads it,
and bpmn-serializer.js serializes it as the element's first child.

The result is that any builder-authored process needing documentation must reach into the built
model and mutate it before export, i.e. drop out of the fluent API for a property the model already
has.

Where this bites, in order of severity:

  1. Ad-hoc sub-process tools (Camunda 8 agentic AI). A tool's <bpmn:documentation> is the
    tool description handed to the LLM — it is required content, not a nicety. Camunda's
    compatibility checks warn Tool documentation is missing for every tool without it, so a
    builder-authored agent is never clean without a post-build mutation pass.
  2. Start events. bpmnkit's own optimizer emits pattern/start-no-documentation ("Start event
    … has no documentation describing expected input variables"), advice the builder gives no way
    to follow.

Verified against @bpmnkit/core@0.1.1.

Reproduction

import { Bpmn } from "@bpmnkit/core";

const defs = Bpmn.createProcess("P")
  .startEvent("S")
  .userTask("Task_Y", { name: "Y", documentation: "Some docs." })   // option silently ignored
  .endEvent("E")
  .build();

console.log(Bpmn.export(defs).includes("Some docs."));   // false  ← option dropped

// The model supports it — only the builder entry point is missing:
defs.processes[0].flowElements.find((e) => e.id === "Task_Y").documentation = "Some docs.";
console.log(Bpmn.export(defs).includes("<bpmn:documentation>Some docs.</bpmn:documentation>"));
// → true

Note the option is silently ignored rather than rejected, which is its own small trap.

Current workaround

Post-build model mutation, which for nested elements means walking into container
flowElements:

const defs = builder.withAutoLayout().build();
const ahsp = defs.processes[0].flowElements.find((e) => e.id === "AIAgent_H");
for (const tool of ahsp.flowElements) {
  tool.documentation = descriptions[tool.id];   // LLM-facing tool description
}
writeFileSync(out, Bpmn.export(defs));

Workable, but it splits one element's definition across the fluent chain and a later imperative
loop, keyed by id — exactly what the typed builder exists to avoid.

Proposed API

Add documentation?: string to ElementOptions, so it is available on every element the builder
can create (including inside SubProcessContentBuilder):

.userTask("Task_Y", { name: "Y", documentation: "Approve if the amount exceeds €500." })

.adHocSubProcess("AIAgent_H", (s) => {
  s.serviceTask("Tool_ListUsers", {
    name: "List users",
    taskType: "io.camunda:http-json:1",
    documentation: "Call this to retrieve all users. Returns id, name, email.",
  });
}, { name: "Handle request" })

No serializer change needed — it already writes documentation as the first child.

Acceptance criteria

  • documentation on ElementOptions, honoured by every builder method on ProcessBuilder,
    SubProcessContentBuilder and BranchBuilder.
  • Emitted as the first child of the element, before <bpmn:extensionElements>.
  • Works on events, gateways, and containers (subProcess / adHocSubProcess), not only tasks.
  • Round-trips through parse → export unchanged.
  • Related: the operations-API round-trip currently drops documentation entirely — see the
    companion compactify/expand issue. Both must hold for builder-set documentation to
    survive a later surgical edit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions