Skip to content

Operations API: compactify/expand round-trip silently drops every <bpmn:documentation> (data loss) #150

Description

@vobu

Summary

The operations API round-trip silently discards every <bpmn:documentation> element.
compactify() does not carry documentation into the compact model, so expand() cannot restore
it — the text is gone from the exported XML with no error and no warning.

This is data loss on the API whose documented purpose is surgical edits that preserve everything
else ("parse → compactify → applyOperations → expand → export, rather than rebuilding"). A caller
who applies a single rename op to one element loses the documentation of all elements in the
file.

bpmn:documentation is not incidental content in Camunda 8:

  • On an ad-hoc sub-process tool it is the tool description handed to the LLM — dropping it
    changes runtime agent behaviour.
  • On a start event it carries the process input contract (bpmnkit's own
    pattern/start-no-documentation optimizer rule asks callers to add it).
  • Toolchains layer review/uncertainty metadata into it.

The rest of the pipeline already handles it correctly: bpmn-parser.js reads documentation,
bpmn-model.d.ts declares documentation?: string, and bpmn-serializer.js writes it as the
first child. Only compact.js drops it.

Verified against @bpmnkit/core@0.1.1.

Reproduction

import { Bpmn, compactify, expand, applyOperations } from "@bpmnkit/core";

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" id="d" targetNamespace="http://bpmn.io/schema/bpmn">
  <bpmn:process id="P">
    <bpmn:startEvent id="S"><bpmn:outgoing>F1</bpmn:outgoing></bpmn:startEvent>
    <bpmn:userTask id="Task_X" name="X">
      <bpmn:documentation>Call this to look up a user by ID.</bpmn:documentation>
      <bpmn:incoming>F1</bpmn:incoming>
    </bpmn:userTask>
    <bpmn:sequenceFlow id="F1" sourceRef="S" targetRef="Task_X"/>
  </bpmn:process>
</bpmn:definitions>`;

// No-op operation list — nothing should change.
const out = Bpmn.export(expand(applyOperations(compactify(Bpmn.parse(xml)), [])));

console.log(xml.includes("Call this to look up a user by ID."));  // true
console.log(out.includes("Call this to look up a user by ID."));  // false  ← lost

Parse alone is fine, which localises the bug to the compact model:

const parsed = Bpmn.parse(xml);
console.log(parsed.processes[0].flowElements.find((e) => e.id === "Task_X").documentation);
// → 'Call this to look up a user by ID.'      ← survives parse
console.log(Bpmn.export(parsed).includes("Call this to look up"));
// → true                                     ← survives parse → export

grep -r documentation dist/bpmn/compact.js returns nothing, while bpmn-parser.js,
bpmn-model.d.ts and bpmn-serializer.js all reference it.

Current workaround

None that keeps the operations API. Callers must avoid compactify/expand entirely on any file
carrying documentation and fall back to rebuilding from the source builder script, or to text
surgery on the exported XML.

Proposed fix

Carry documentation through the compact model like any other scalar element property:

  1. compact.js — copy documentation onto the compact element when compactifying, and back onto
    the expanded flow element in expand().
  2. Optionally accept documentation in the update op's patch (it already works by accident via
    Object.assign, once the round-trip stops dropping it).

Acceptance criteria

  • expand(compactify(parse(xml)))export preserves every <bpmn:documentation>, on all
    element types, including elements nested inside sub-processes and ad-hoc sub-processes.
  • An empty operation list is a byte-identical round-trip with respect to documentation.
  • { op: "update", id, patch: { documentation: "…" } } sets it.
  • Regression test covering a documentation-bearing element that is also the target of a
    rename / redirect_flow op.

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