@formicoidea/labre-framework-bpmn 0.31.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/actions.d.ts +213 -0
  2. package/dist/actions.js +467 -0
  3. package/dist/background.d.ts +2 -0
  4. package/dist/background.js +158 -0
  5. package/dist/commands.d.ts +4 -0
  6. package/dist/commands.js +567 -0
  7. package/dist/consts.d.ts +157 -3
  8. package/dist/consts.js +192 -3
  9. package/dist/descriptor.d.ts +8 -3
  10. package/dist/descriptor.js +6 -3
  11. package/dist/element-renderer.d.ts +10 -4
  12. package/dist/element-renderer.js +14 -55
  13. package/dist/element-view.d.ts +100 -8
  14. package/dist/element-view.js +249 -30
  15. package/dist/export.d.ts +277 -0
  16. package/dist/export.js +1802 -0
  17. package/dist/facts.d.ts +48 -0
  18. package/dist/facts.js +127 -0
  19. package/dist/import.d.ts +44 -0
  20. package/dist/import.js +1440 -0
  21. package/dist/index.d.ts +14 -1
  22. package/dist/index.js +46 -1
  23. package/dist/interchange.d.ts +109 -0
  24. package/dist/interchange.js +191 -0
  25. package/dist/morph.d.ts +61 -0
  26. package/dist/morph.js +118 -0
  27. package/dist/node/node-renderer.d.ts +0 -9
  28. package/dist/node/node-renderer.js +294 -17
  29. package/dist/pool-hit.d.ts +98 -0
  30. package/dist/pool-hit.js +130 -0
  31. package/dist/presets.d.ts +114 -0
  32. package/dist/presets.js +232 -0
  33. package/dist/profiles.d.ts +2 -0
  34. package/dist/profiles.js +189 -0
  35. package/dist/roles.d.ts +96 -0
  36. package/dist/roles.js +410 -0
  37. package/dist/rules.d.ts +199 -0
  38. package/dist/rules.js +1539 -0
  39. package/dist/templates/index.js +116 -9
  40. package/dist/toolbar/bpmn-menu.d.ts +6 -21
  41. package/dist/toolbar/bpmn-menu.js +6 -173
  42. package/dist/toolbar/bpmn-senior-button.js +8 -2
  43. package/dist/toolbar/config.d.ts +27 -2
  44. package/dist/toolbar/config.js +86 -2
  45. package/dist/toolbar/icons.d.ts +67 -0
  46. package/dist/toolbar/icons.js +141 -0
  47. package/dist/toolbar/senior-tool.js +1 -0
  48. package/dist/translations.d.ts +16 -0
  49. package/dist/translations.js +20 -0
  50. package/dist/view.d.ts +18 -0
  51. package/dist/view.js +95 -7
  52. package/package.json +2 -2
package/dist/rules.js ADDED
@@ -0,0 +1,1539 @@
1
+ import { BPMN_POOL_BACKGROUND } from './background.js';
2
+ import { BPMN_ROLE, BPMN_ROLES } from './roles.js';
3
+ /**
4
+ * BPMN validation rules — the descriptive profile, as DATA (backlog item B6).
5
+ *
6
+ * NOTE ON NUMBERING: that "B6" is the BACKLOG item this file delivers. The
7
+ * **B1**–**B21** headings below are RULE numbers, a different space entirely,
8
+ * and the two collide on one token. Every in-file cross-reference means the rule
9
+ * number; the backlog item is named nowhere else.
10
+ *
11
+ * DATA owned by the framework, versioned per rule: the engine
12
+ * (`@labre/affine-block-surface`) knows how to evaluate a FAMILY, never a
13
+ * concrete rule, so adding a BPMN rule is adding an entry to the array at the
14
+ * bottom of this file. Registered from the flag-gated `BpmnViewExtension`, so
15
+ * switching the `bpmn` flag off removes the rules with the rest of the tooling
16
+ * — processes already drawn keep rendering, they simply stop being checked
17
+ * (`docs/adr/0009`).
18
+ *
19
+ * ## Every rule is read off the SPEC, and cites where
20
+ *
21
+ * The page references below are to **BPMN 2.0.2 (OMG / ISO-IEC 19510)**, which
22
+ * the PO supplied. They are not decoration: three rules in the first draft of
23
+ * this pack fired on diagrams the specification explicitly sanctions, and the
24
+ * only thing that caught them was reading the normative text rather than the
25
+ * folklore.
26
+ *
27
+ * ## And every rule now DECLARES where its authority comes from
28
+ *
29
+ * The citations above used to live in these comments alone, which meant the UI
30
+ * could not tell a conformance defect from a house style: a Labre convention
31
+ * presented to an architect as a norm violation is the finding an external
32
+ * review will not forgive. {@link ValidationRule.provenance} promotes the
33
+ * distinction to data — `standard` for a normative MUST with its page,
34
+ * `recommendation` for a SHOULD or an industry linter, `labre-convention` for
35
+ * ours — and the violation bubble says which.
36
+ *
37
+ * Two rules here are `labre-convention` and neither pretends otherwise:
38
+ * {@link sequenceFlowSelfLoop} and {@link untypedFlow}. The first exists as a
39
+ * separate rule BECAUSE of this field: it was a clause of B1, whose matrix is
40
+ * p.95, and one rule cannot honestly declare two provenances — see B1a.
41
+ *
42
+ * ## Why BPMN is the framework that needed five families
43
+ *
44
+ * A Wardley map is judged element by element: where a thing sits, which way an
45
+ * arrow points. A process is judged by its JOINTS. Every question below is about
46
+ * a relation, a count of relations, the frames two ends sit in, or the graph the
47
+ * relations build — which is why four of the five families arrived with the
48
+ * engine work that preceded this file, and why none of them names BPMN.
49
+ *
50
+ * - `relation-endpoints` (B1–B5) — WHAT a link may run between, and how many
51
+ * times. The grammar.
52
+ * - `edge-degree` (B6–B10, B16–B19) — HOW MANY links arrive at and leave one
53
+ * symbol. The half no per-link rule can express: nothing is wrong with any
54
+ * single arrow, the mistake is the count.
55
+ * - `edge-locality` (B11–B12) — WHICH POOL each end sits in. The swimlane
56
+ * question, and the one where the two ends carry identical roles in the legal
57
+ * case and the illegal one.
58
+ * - `role-count` (B14–B15, B20) — what one pool must CONTAIN, and only when it
59
+ * already contains the other half of the pair.
60
+ * - `reachability` (B13) — can you get there from the start. The graph question.
61
+ * - `label-presence` (B21) — does the symbol carry a name at all.
62
+ *
63
+ * ## The industry linters, and where we deliberately differ
64
+ *
65
+ * The inventory was triangulated against **bpmnlint** (the bpmn-js ecosystem's
66
+ * linter, the one a BPMN practitioner will already have met),
67
+ * bpmn-visualization-js and JointJS. Where a bpmnlint rule reads the same
68
+ * normative sentence we do, the id and the wording are chosen so a user
69
+ * recognises it; where we are quieter, the rule comment says why in its own
70
+ * paragraph. Two of theirs are NOT adopted:
71
+ *
72
+ * - **`no-implicit-start`** (ERROR there). It contradicts p.238: a Process is not
73
+ * required to contain a Start Event, and a flow object with no incoming
74
+ * sequence flow is then a legitimate parallel start (p.245). We sided with the
75
+ * specification — that decision is what
76
+ * {@link ReachabilityDef.implicitRoots} implements, and adopting the
77
+ * linter's rule would have meant shipping the two in contradiction.
78
+ * - **any overlap / readability rule.** The engine's pair-wise budget is pinned
79
+ * by the Wardley bench, which asserts that `wardley.overlapping-artefacts` is
80
+ * the ONLY registered `no-overlap` rule — a second one triggers the
81
+ * spatial-index obligation. That is a piece of engine work with its own
82
+ * measurements, not a line of data, so it is a backlog item and not this file.
83
+ *
84
+ * ## Severity: `warning` almost everywhere, `audit` where the spec allows the
85
+ * shape, and `blocking-overridable` nowhere
86
+ *
87
+ * The same promise `wardley/rules.ts:30` makes, for the same reason: NOTHING
88
+ * downstream implements a blocking level — no gesture is refused anywhere in
89
+ * this library — so shipping the value would be data claiming an effect that
90
+ * does not exist. Several of these are normative MUSTs and would sit there; they
91
+ * move in one line each, in `profiles.ts`, the day the gesture refusal lands.
92
+ *
93
+ * Five rules are declared `audit` on their own — `bpmn.activity-dead-end`,
94
+ * `bpmn.fake-join`, `bpmn.implicit-split`, `bpmn.single-blank-start` and
95
+ * `bpmn.unlabeled-step` — because in each case the specification sanctions the
96
+ * shape they report, or the diagram is merely unfinished. They are NUANCES for
97
+ * the conformance panel, never warnings on the canvas, and every one of them is
98
+ * quieter than the corresponding bpmnlint level on purpose.
99
+ *
100
+ * The severities declared here are the DESCRIPTIVE posture; `bpmn.sketch` — the
101
+ * default — demotes every one of them to `audit`, so the drawing hand is never
102
+ * argued with until someone asks for the stricter profile. See `profiles.ts`.
103
+ *
104
+ * ## What the whole file stays silent about
105
+ *
106
+ * A process drawn before the roles existed carries no role on anything, so it is
107
+ * never evaluated and never says a word (PRD principle 8). A process drawn
108
+ * before anybody added a pool has no frame for B11–B15 and B20 to be about, and those
109
+ * six are silent by construction.
110
+ *
111
+ * ## One thing the `relation-endpoints` family cannot say
112
+ *
113
+ * The ALPHABET of a `relation-endpoints` rule is derived from the roles its
114
+ * sanctioned triplets name. That is what makes the family proportionate — an end
115
+ * outside the alphabet takes the whole link out of the conversation, so a flow
116
+ * drawn onto a sticky note is a draft and not a finding — and it is also its one
117
+ * limit: **a framework cannot name a role it wants to REJECT without sanctioning
118
+ * a sentence for it.**
119
+ *
120
+ * Three normative prohibitions are therefore SILENCE rather than findings today:
121
+ *
122
+ * - "an Artifact MUST NOT be the source or target of a Sequence Flow" (p.65) and
123
+ * "a Sequence Flow connects Events, Activities and Gateways" (p.95) — a
124
+ * sequence flow dropped on a text annotation or a data object goes unjudged;
125
+ * - the Message Flow Connection Rules table (p.41–42), which lists what a
126
+ * message may run between and then says "Thus, Lane, Gateway, Data Object,
127
+ * Group, and Text Annotation are not listed in the table" — a gateway is
128
+ * named by no message sentence, so it is outside B2's alphabet.
129
+ *
130
+ * Reported to the engine author rather than worked around here: a triplet
131
+ * written to widen an alphabet without meaning what it says would be exactly the
132
+ * data-that-lies this platform refuses everywhere else. The corpus pins the
133
+ * silence explicitly (`corpus.unit.spec.ts`), so it stays a known limit rather
134
+ * than an assumption.
135
+ *
136
+ * ## The engine fields this pack asked for, and now uses
137
+ *
138
+ * Six of the rules below were authored against defs that did not exist when they
139
+ * were written, and `claude/bpmn-engine-v2` (#145) landed all six:
140
+ * `RoleCountDef.ifPresent` and `.exact`, `EdgeDegreeDef.forbidPattern` and
141
+ * `.eitherMin`, `ReachabilityDef.implicitRoots`, and the `label-presence`
142
+ * family. Every one of the rules below is registered and live.
143
+ *
144
+ * Two of them are worth knowing about at the call site:
145
+ *
146
+ * - `forbidPattern` carries its OWN {@link RuleMessage} inside the pattern
147
+ * rather than in a slot beside the other bounds, because a forbidden zone is
148
+ * not a bound that failed and its sentence never reads like one;
149
+ * - `label-presence` reads the subject's OWN `text`. A framework whose artefacts
150
+ * are named by a separate element beside them is asking a different question,
151
+ * and this family cannot answer it. BPMN names its steps in place, so it can.
152
+ */
153
+ /**
154
+ * `eitherMin` was also shipped, and this pack deliberately does not use it.
155
+ *
156
+ * It and `forbidPattern: { maxIn: 1, maxOut: 1 }` select exactly the same set of
157
+ * nodes — the engine ships a test asserting they agree across the whole degree
158
+ * space — so {@link gatewayMustBranch} could be written either way. It is
159
+ * written as a forbidden zone because that is what its SENTENCE says: "this
160
+ * gateway neither splits nor merges" describes a shape the diagram has, not a
161
+ * count it is missing, and the data reads best when it says what the message
162
+ * says. Nothing is wrong with the other reading; a rule simply has to pick one.
163
+ */
164
+ /**
165
+ * The one sanctioned sentence of a sequence flow: a flow object is followed by a
166
+ * flow object (p.95 — "a Sequence Flow connects Events, Activities and
167
+ * Gateways").
168
+ *
169
+ * Written on `bpmn:flow-object` rather than on the three families under it, which
170
+ * is what makes it ONE triplet instead of nine: `roleIsA` resolves it for every
171
+ * event, activity and gateway, and for whatever the descriptive profile adds
172
+ * under them later.
173
+ *
174
+ * Exported so a test asserts THIS table rather than a copy of it, and so B4
175
+ * declares the same alphabet it judges neutral links against without restating
176
+ * it — see {@link untypedFlow}.
177
+ */
178
+ export const BPMN_SEQUENCE_MATRIX = [
179
+ {
180
+ source: BPMN_ROLE.flowObject,
181
+ edge: BPMN_ROLE.sequenceFlow,
182
+ target: BPMN_ROLE.flowObject,
183
+ },
184
+ ];
185
+ /**
186
+ * **B1** — a sequence flow chains steps of the same process.
187
+ *
188
+ * The base grammar (p.95). Its matrix holds ONE sentence, and
189
+ * `bpmn:flow-object` is therefore the whole alphabet of the rule — which makes
190
+ * the off-matrix branch structurally unreachable, exactly like Context Mapping's
191
+ * CM1, and for exactly the same reason: **the matrix is here to declare the
192
+ * alphabet**.
193
+ *
194
+ * That alphabet is what makes the rule proportionate, and it is also where two
195
+ * normative prohibitions go quiet: an Artifact — a text annotation, a group —
196
+ * must not be a sequence flow endpoint (p.65), and neither must a data element
197
+ * (p.95), but neither is named by the sentence above, so a flow dropped on one is
198
+ * outside the conversation. See the file header; the corpus pins it.
199
+ *
200
+ * ## The self-loop LEFT this rule, and the provenance field is why
201
+ *
202
+ * Until the provenance work it also carried `forbidSelfLoop`, and that clause
203
+ * was the only one of the two that could ever fire — which made the rule's
204
+ * authority a genuine mix: a matrix out of p.95 whose sentence is unreachable,
205
+ * plus a house style with no page behind it. Declared `standard`, it would have
206
+ * shown an OMG citation under a finding the OMG does not make; declared
207
+ * `labre-convention`, it would have disowned p.95. So the clause became
208
+ * {@link sequenceFlowSelfLoop}, B1a — the same split, for the same reason, that
209
+ * already separated {@link duplicateSequenceFlow} from this rule.
210
+ *
211
+ * ## Which leaves this rule with NO reachable finding at all, deliberately
212
+ *
213
+ * Said out loud, because a file this careful about recording its own silences
214
+ * should not leave the reader to derive this one. `evaluateRelationEndpoints`
215
+ * has four raise sites — off-matrix, self-loop, duplicate, exclusive-pair — and
216
+ * B1 can reach none of them:
217
+ *
218
+ * - the ALPHABET GATE runs first and admits only roles the triplets name. The
219
+ * matrix is one sentence over `bpmn:flow-object`, so the alphabet is
220
+ * `{flow-object}`, so anything that survives the gate is a flow object at both
221
+ * ends — and `inMatrix` is then unconditionally true. The off-matrix branch
222
+ * cannot fire for the same reason it exists;
223
+ * - `forbidSelfLoop`, `forbidDuplicate`, `exclusivePairs` and `flagNeutral` are
224
+ * all absent, so the other three sites are never even consulted.
225
+ *
226
+ * That is not a defect and not dead data. The rule is the DECLARED SENTENCE and
227
+ * its citation — the one place the grammar of a sequence flow is written down as
228
+ * data — and the alphabet it publishes is consumed by the two rules that do
229
+ * fire: {@link sequenceFlowSelfLoop} and {@link untypedFlow} both read
230
+ * {@link BPMN_SEQUENCE_MATRIX}, so none of the three can ever disagree about
231
+ * what "a step" is. A profile line that can raise nothing is the price of
232
+ * keeping the sentence and its page in one reviewable object; the alternative is
233
+ * a matrix inlined at two call sites with the citation attached to neither.
234
+ */
235
+ const sequenceFlowEndpoints = {
236
+ id: 'bpmn.sequence-flow-endpoints',
237
+ framework: 'bpmn',
238
+ family: 'relation-endpoints',
239
+ severity: 'warning',
240
+ // No `appliesTo`: the subject is a RELATION, and the role that names it is
241
+ // declared where the family reads it — naming one of the three indicted
242
+ // elements here would be data that lies.
243
+ roles: BPMN_ROLES,
244
+ messageKey: 'com.labre.bpmn.validation.sequence-flow-endpoints',
245
+ messageFallback: 'This sequence flow does not chain two steps of a process.',
246
+ suggestionKey: 'com.labre.bpmn.validation.sequence-flow-endpoints.suggestion',
247
+ suggestionFallback: 'A sequence flow runs between events, activities and gateways of one process — between pools, send a message flow instead.',
248
+ version: 1,
249
+ provenance: {
250
+ source: 'standard',
251
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.95 — a Sequence Flow connects Events, Activities and Gateways',
252
+ },
253
+ // Not a frame the rule measures against — a sentence is right or wrong
254
+ // wherever it is drawn — but the pool a finding is ATTRIBUTED to, so the
255
+ // arbitration "ignore this rule on this participant" has somewhere to live.
256
+ // The `wardley.overlapping-artefacts` pattern, and the reason no background
257
+ // DECLARATION is carried: this rule reads no geometry at all.
258
+ backgroundRole: BPMN_ROLE.pool,
259
+ endpoints: {
260
+ edgeRole: BPMN_ROLE.sequenceFlow,
261
+ allowed: BPMN_SEQUENCE_MATRIX,
262
+ // Deliberately NOT `forbidDuplicate`, and no longer `forbidSelfLoop`. Two
263
+ // sequence flows between the same two steps is how a hand draws a process
264
+ // it is still thinking about, and on a descriptive diagram it is read by
265
+ // following arrows rather than by counting them — the Event Storming call,
266
+ // for the same reason, and the opposite of the Context Mapping one where
267
+ // the same pattern twice is a claim made twice.
268
+ },
269
+ };
270
+ /**
271
+ * **B1a** — and never a step onto itself. A LABRE convention, said out loud.
272
+ *
273
+ * The specification contains no normative prohibition on a Sequence Flow whose
274
+ * source and target are the same flow object; the "no self-loop" habit comes
275
+ * from the BPMN 1.x loop idiom, which 2.0 replaced with activity loop MARKERS
276
+ * and standard loop characteristics. There is no page to cite, and this rule
277
+ * says so in data ({@link ValidationRule.provenance}) rather than only in the
278
+ * apologetic clause of its own suggestion.
279
+ *
280
+ * We keep it, at `warning`, because on a DESCRIPTIVE diagram a step drawn as
281
+ * following itself tells the reader nothing about what decides to repeat it —
282
+ * and the notation that does is one gateway away.
283
+ *
284
+ * ## Why it is a rule of its own, and not a clause of B1
285
+ *
286
+ * Because provenance describes THE RULE. B1's matrix is p.95 and this is our
287
+ * house style, so one object holding both could not declare either honestly —
288
+ * and the half that fires is this one, so the bubble would have shown an OMG
289
+ * page under a finding the OMG does not make. That is precisely the
290
+ * "conventions presented as norm violations" the architecture review flagged.
291
+ *
292
+ * The composition is the one {@link duplicateSequenceFlow} already proves: two
293
+ * `relation-endpoints` rules, same `edgeRole`, same alphabet, one flag each.
294
+ * What actually makes them safe to compose is the ALPHABET GATE, not the order
295
+ * of the checks inside the walk: the gate drops any edge whose ends are outside
296
+ * the triplets' roles BEFORE either the self-loop test or the matrix test is
297
+ * reached, so an off-alphabet self-loop — a link looped onto a group, an
298
+ * annotation, a data object, a pool — was silence before the split and is
299
+ * silence after it. Past the gate, the self-loop test comes first and
300
+ * `continue`s, and B1 declares no `forbidSelfLoop`, so exactly one of the two
301
+ * rules can indict a given loop. (An earlier draft of this comment said only
302
+ * "the self-loop is judged before the matrix": true, but it is the gate that
303
+ * carries the argument.)
304
+ *
305
+ * It carries no `selfLoop` block: with the matrix unreachable, the rule's OWN
306
+ * message is the self-loop message, and the family falls back to it. The two
307
+ * i18n keys are the ones the clause shipped with, unchanged.
308
+ *
309
+ * ## What the split DID rename, and why that was acceptable exactly once
310
+ *
311
+ * The `ruleId`. And a rule id is not only a code identifier: user exceptions
312
+ * ({@link ValidationRule} arbitrations, PF8) are persisted on the element in
313
+ * `validationExceptions` and keyed BY rule id, so a document where somebody had
314
+ * excused a self-loop under `bpmn.sequence-flow-endpoints` sees that finding
315
+ * come back under this id, its arbitration orphaned.
316
+ *
317
+ * That was acceptable here for one reason only: the BPMN pack shipped days
318
+ * before this change and the packages are unpublished, so the set of affected
319
+ * documents is empty in practice. It would NOT be acceptable after publication,
320
+ * and nothing about this precedent should be read as saying otherwise —
321
+ * renaming a rule id orphans stored exceptions, and past publication that costs
322
+ * either a migration or an alias in `hasException`.
323
+ */
324
+ const sequenceFlowSelfLoop = {
325
+ id: 'bpmn.sequence-flow-self-loop',
326
+ framework: 'bpmn',
327
+ family: 'relation-endpoints',
328
+ severity: 'warning',
329
+ roles: BPMN_ROLES,
330
+ messageKey: 'com.labre.bpmn.validation.sequence-flow-self-loop',
331
+ messageFallback: 'This sequence flow loops back onto the step it leaves.',
332
+ suggestionKey: 'com.labre.bpmn.validation.sequence-flow-self-loop.suggestion',
333
+ suggestionFallback: 'BPMN does not forbid this, but a descriptive diagram reads better when what decides to repeat a step is drawn: send the flow back through a gateway, or mark the activity as a loop.',
334
+ version: 1,
335
+ provenance: {
336
+ source: 'labre-convention',
337
+ reference: 'Labre house style — BPMN 2.0.2 states no prohibition; the 1.x loop idiom was replaced by activity loop markers',
338
+ },
339
+ backgroundRole: BPMN_ROLE.pool,
340
+ endpoints: {
341
+ edgeRole: BPMN_ROLE.sequenceFlow,
342
+ // The alphabet, and only the alphabet: the matrix cannot fire here (B1 owns
343
+ // the sentence) and is present so that a flow onto an annotation, a
344
+ // document or a pool stays outside the conversation. Same table B1
345
+ // sanctions, so the two can never disagree about what "a step" is.
346
+ allowed: BPMN_SEQUENCE_MATRIX,
347
+ forbidSelfLoop: true,
348
+ },
349
+ };
350
+ /**
351
+ * **B2** — a message flow leaves what SENDS and arrives at what RECEIVES.
352
+ *
353
+ * The other flow, and the other verb: a sequence flow orders work inside one
354
+ * participant, a message flow carries a message between two, and says nothing
355
+ * about order at all.
356
+ *
357
+ * The four sanctioned sentences are the DIRECTED send/receive halves of the
358
+ * notation, and the direction is the whole rule — the triplets are read
359
+ * `source → target`, so encoding it needs nothing but the table:
360
+ *
361
+ * - an **activity** both sends and receives (p.152, the Activity's own Message
362
+ * Flow Connections; the table at p.41–42 is what says which artefacts may be
363
+ * an end at all);
364
+ * - an **end event** only SENDS. A message end event throws on the way out, and
365
+ * nothing arrives at an end because the instance is over (p.248);
366
+ * - a **start event** only RECEIVES. A message start event is a participant being
367
+ * woken up by somebody else's message; it has nothing to send yet (p.245).
368
+ *
369
+ * The two reachable findings are therefore the two inversions — a message drawn
370
+ * OUT of a start event, and a message drawn INTO an end event — and they are the
371
+ * two a reader cannot recover from, because both say the instance runs backwards.
372
+ *
373
+ * ## What it is silent about
374
+ *
375
+ * A message flow onto a GATEWAY, which the Message Flow Connection Rules table
376
+ * excludes outright (p.41–42: "Thus, Lane, Gateway, Data Object, Group, and Text
377
+ * Annotation are not listed in the table"). A gateway is named by no sentence
378
+ * above, so it is outside the alphabet — see the file header, where this is
379
+ * recorded as an engine limit rather than a decision.
380
+ * Text annotations and data elements are outside it too, for the ordinary
381
+ * reason: they are not endpoints of a message.
382
+ *
383
+ * Pools are outside it as well, and there it is the RIGHT answer in this editor:
384
+ * a pool is not a connectable artefact here, so a message between two
385
+ * participants is drawn between the artefacts inside them. The locality half —
386
+ * "a Message Flow MUST connect two separate Pools" (p.119) — is B12's question,
387
+ * and B12 deliberately stays silent when an end sits inside no pool at all: the
388
+ * sketch primes.
389
+ */
390
+ const messageFlowEndpoints = {
391
+ id: 'bpmn.message-flow-endpoints',
392
+ framework: 'bpmn',
393
+ family: 'relation-endpoints',
394
+ severity: 'warning',
395
+ roles: BPMN_ROLES,
396
+ messageKey: 'com.labre.bpmn.validation.message-flow-endpoints',
397
+ messageFallback: 'This message flow runs out of something that only receives, or into something that only sends.',
398
+ suggestionKey: 'com.labre.bpmn.validation.message-flow-endpoints.suggestion',
399
+ suggestionFallback: 'A message leaves an activity or an end event, and arrives at an activity or a start event: a start event is woken up by a message and has none to send, and an end event is where the instance stops.',
400
+ version: 1,
401
+ provenance: {
402
+ source: 'standard',
403
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.41–42 (Message Flow Connection Rules), p.152, p.245, p.248',
404
+ },
405
+ backgroundRole: BPMN_ROLE.pool,
406
+ endpoints: {
407
+ edgeRole: BPMN_ROLE.messageFlow,
408
+ allowed: [
409
+ // An activity sends, and an activity receives (p.152).
410
+ {
411
+ source: BPMN_ROLE.activity,
412
+ edge: BPMN_ROLE.messageFlow,
413
+ target: BPMN_ROLE.activity,
414
+ },
415
+ // An activity wakes a participant up (p.245).
416
+ {
417
+ source: BPMN_ROLE.activity,
418
+ edge: BPMN_ROLE.messageFlow,
419
+ target: BPMN_ROLE.startEvent,
420
+ },
421
+ // An end event throws on the way out (p.248)…
422
+ {
423
+ source: BPMN_ROLE.endEvent,
424
+ edge: BPMN_ROLE.messageFlow,
425
+ target: BPMN_ROLE.activity,
426
+ },
427
+ // …including to the participant it wakes up.
428
+ {
429
+ source: BPMN_ROLE.endEvent,
430
+ edge: BPMN_ROLE.messageFlow,
431
+ target: BPMN_ROLE.startEvent,
432
+ },
433
+ ],
434
+ },
435
+ };
436
+ /**
437
+ * **B3** — an association ties a note, or a document, to the work.
438
+ *
439
+ * The third connecting object, and the only one that is not a flow: it carries
440
+ * no token, states no order, and — as `roles.ts` says at length — has no verb, so
441
+ * it reads identically from either end. That is why every sentence below is
442
+ * declared in BOTH directions: the notation does not distinguish them, and a
443
+ * rule that did would indict a hand for dragging the other way round.
444
+ *
445
+ * Our vocabulary has ONE association role, so this one rule covers both of the
446
+ * spec's kinds:
447
+ *
448
+ * - a plain **Association** attaches a text annotation to whatever it comments
449
+ * on — and an annotation may comment on anything, including the pool itself;
450
+ * - a **DataAssociation** ties a data object or a data store to the activity or
451
+ * event that reads or produces it (p.222). Never to a gateway: a gateway
452
+ * decides, it does not handle paperwork.
453
+ *
454
+ * ## The two reachable findings
455
+ *
456
+ * A gateway IS in the alphabet — it is a flow object, which the annotation
457
+ * sentences name — so an association from a gateway to a data element is a
458
+ * finding rather than silence. And so is an association between two DATA
459
+ * elements: data is named as an endpoint, so it is in the alphabet, and no
460
+ * sentence relates one document to another. The drawn endpoints of a data
461
+ * association join data to WORK.
462
+ *
463
+ * An association drawn between two tasks is a finding too — somebody reached for
464
+ * a sequence flow and got the undirected line.
465
+ */
466
+ const associationEndpoints = {
467
+ id: 'bpmn.association-endpoints',
468
+ framework: 'bpmn',
469
+ family: 'relation-endpoints',
470
+ severity: 'warning',
471
+ roles: BPMN_ROLES,
472
+ messageKey: 'com.labre.bpmn.validation.association-endpoints',
473
+ messageFallback: 'This association does not tie a note or a document to the work.',
474
+ suggestionKey: 'com.labre.bpmn.validation.association-endpoints.suggestion',
475
+ suggestionFallback: 'An association attaches a text annotation to what it comments on, or a data object to the step that reads or produces it. To chain two steps, draw a sequence flow instead.',
476
+ version: 1,
477
+ // The clause is read off the specification's own table of contents, not
478
+ // guessed from the shape of a neighbouring one: §8.3.1 Artifacts is where
479
+ // "BPMN provides three standard Artifacts: Associations, Groups, and Text
480
+ // Annotations" is written. §8.3.13 — which an earlier draft of this line
481
+ // cited — is Sequence Flow, and clause 8.3 runs to 8.3.14.
482
+ provenance: {
483
+ source: 'standard',
484
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) §8.3.1 Artifacts, p.65–66 — Association and Text Annotation; p.222 — Data Associations',
485
+ },
486
+ backgroundRole: BPMN_ROLE.pool,
487
+ endpoints: {
488
+ edgeRole: BPMN_ROLE.association,
489
+ allowed: [
490
+ // A note about a step…
491
+ {
492
+ source: BPMN_ROLE.textAnnotation,
493
+ edge: BPMN_ROLE.association,
494
+ target: BPMN_ROLE.flowObject,
495
+ },
496
+ {
497
+ source: BPMN_ROLE.flowObject,
498
+ edge: BPMN_ROLE.association,
499
+ target: BPMN_ROLE.textAnnotation,
500
+ },
501
+ // …a note about a document…
502
+ {
503
+ source: BPMN_ROLE.textAnnotation,
504
+ edge: BPMN_ROLE.association,
505
+ target: BPMN_ROLE.data,
506
+ },
507
+ {
508
+ source: BPMN_ROLE.data,
509
+ edge: BPMN_ROLE.association,
510
+ target: BPMN_ROLE.textAnnotation,
511
+ },
512
+ // …a note about the participant itself. An annotation comments on anything
513
+ // it is pointed at, and the pool is the one thing on the board a reader
514
+ // most often wants a word about.
515
+ {
516
+ source: BPMN_ROLE.textAnnotation,
517
+ edge: BPMN_ROLE.association,
518
+ target: BPMN_ROLE.pool,
519
+ },
520
+ {
521
+ source: BPMN_ROLE.pool,
522
+ edge: BPMN_ROLE.association,
523
+ target: BPMN_ROLE.textAnnotation,
524
+ },
525
+ // …and the document the work reads or produces (p.222). Activities and
526
+ // events only: a gateway decides, it does not handle paperwork.
527
+ {
528
+ source: BPMN_ROLE.data,
529
+ edge: BPMN_ROLE.association,
530
+ target: BPMN_ROLE.activity,
531
+ },
532
+ {
533
+ source: BPMN_ROLE.activity,
534
+ edge: BPMN_ROLE.association,
535
+ target: BPMN_ROLE.data,
536
+ },
537
+ {
538
+ source: BPMN_ROLE.data,
539
+ edge: BPMN_ROLE.association,
540
+ target: BPMN_ROLE.event,
541
+ },
542
+ {
543
+ source: BPMN_ROLE.event,
544
+ edge: BPMN_ROLE.association,
545
+ target: BPMN_ROLE.data,
546
+ },
547
+ ],
548
+ },
549
+ };
550
+ /**
551
+ * **B4** — a plain connector between two steps is a flow nobody typed.
552
+ *
553
+ * The gap every other rule in this file falls through. BPMN's own link tools
554
+ * stamp a role; quick-connect and auto-complete do not, so releasing the canvas'
555
+ * link gesture between two tasks produces a connector carrying NO role — which
556
+ * says nothing to any grammar, any degree count, any locality check and any
557
+ * traversal. A process joined that way LOOKS connected and validates as if
558
+ * nobody had joined anything: every start event has no exit, every end event is
559
+ * unreached, every step is an orphan, and the diagram on screen shows arrows.
560
+ *
561
+ * So the rule is not really about the connector: it is about the fourteen
562
+ * verdicts that go quiet behind it.
563
+ *
564
+ * ## Why it declares a matrix it never judges anything against
565
+ *
566
+ * `flagNeutral` reads the rule's own ALPHABET to decide which role-less links it
567
+ * may presume were meant as relations, so the matrix is how this rule says
568
+ * "between two steps" — and, by saying only that, how it stays silent about
569
+ * everything else. A plain connector onto a POOL, onto a data object, onto a
570
+ * text annotation, onto a sticky note is an ANNOTATION and none of BPMN's
571
+ * business: pointing at things is what a whiteboard is for.
572
+ *
573
+ * Nothing else in the declaration fires. The matrix is the same single sentence
574
+ * B1 sanctions, so no typed sequence flow can be off it; `forbidSelfLoop` and
575
+ * `forbidDuplicate` are absent, so loops are B1a's business and copies are B5's,
576
+ * and neither is ever reported twice. This rule raises exactly one kind of
577
+ * finding.
578
+ *
579
+ * No page: the specification has nothing to say about a connector the notation
580
+ * does not contain. This is a rule about OUR canvas, and it exists because our
581
+ * canvas has a gesture BPMN never anticipated.
582
+ */
583
+ const untypedFlow = {
584
+ id: 'bpmn.untyped-flow',
585
+ framework: 'bpmn',
586
+ family: 'relation-endpoints',
587
+ severity: 'warning',
588
+ roles: BPMN_ROLES,
589
+ // The rule's own words are never read: `flagNeutral` is the only verdict it
590
+ // can reach, and it carries its own. Declared all the same, because the shape
591
+ // requires them and a rule with no sentence at all is a rule nobody can review.
592
+ messageKey: 'com.labre.bpmn.validation.untyped-flow',
593
+ messageFallback: 'This link between two steps says nothing the model records.',
594
+ suggestionKey: 'com.labre.bpmn.validation.untyped-flow.suggestion',
595
+ suggestionFallback: 'Draw a sequence flow to order two steps of one process, or a message flow to send something between two participants. A plain connector is drawn on the diagram and absent from the model.',
596
+ version: 1,
597
+ // No page, and none is possible: the specification has nothing to say about a
598
+ // connector the notation does not contain. This is a rule about OUR canvas.
599
+ provenance: {
600
+ source: 'labre-convention',
601
+ reference: 'Labre convention — a role-less connector is a gesture of this canvas, not an artefact of the notation',
602
+ },
603
+ backgroundRole: BPMN_ROLE.pool,
604
+ endpoints: {
605
+ edgeRole: BPMN_ROLE.sequenceFlow,
606
+ // The alphabet, and nothing else: see the header. Same table B1 sanctions,
607
+ // so the two can never disagree about what "a step" is.
608
+ allowed: BPMN_SEQUENCE_MATRIX,
609
+ flagNeutral: {
610
+ messageKey: 'com.labre.bpmn.validation.untyped-flow.neutral',
611
+ messageFallback: 'These two steps are joined by an untyped link.',
612
+ suggestionKey: 'com.labre.bpmn.validation.untyped-flow.neutral.suggestion',
613
+ suggestionFallback: 'The diagram shows an arrow and the process holds none — nothing follows anything here. Draw it again with the sequence flow tool, or with the message flow tool if it crosses between participants.',
614
+ },
615
+ },
616
+ };
617
+ /**
618
+ * **B5** — the same flow drawn twice between the same two steps.
619
+ *
620
+ * bpmnlint's `no-duplicate-sequence-flows`, which the bpmn-js ecosystem raises
621
+ * at ERROR level, and a capability the engine has had since Context Mapping
622
+ * asked for it — this rule is one flag, no engine work at all.
623
+ *
624
+ * Two arrows from A to B are one arrow drawn twice: the second says nothing the
625
+ * first did not, and it makes the diagram read as if the process reached B twice.
626
+ * Where a reader IS meant to see two routes, the routes leave from different
627
+ * places — that is what a gateway is for.
628
+ *
629
+ * ## Why B1 does not carry the flag, and this rule does
630
+ *
631
+ * Because they are different requirements at different levels of tolerance, and
632
+ * a profile has to be able to move one without the other. B1's self-loop is a
633
+ * LABRE convention; this one is an industry rule with a normative-looking level
634
+ * in every linter BPMN users have met. Keeping them apart also keeps the "one
635
+ * mistake, one sentence" promise: `evaluateRelationEndpoints` reports at most
636
+ * one finding per edge, so an edge indicted for its sentence is never also
637
+ * indicted for being a copy — but only if the two rules are separate objects.
638
+ *
639
+ * ## What counts as a duplicate
640
+ *
641
+ * The same role AND the same ORDERED pair. A → B and B → A are two different
642
+ * sentences — a request and its answer — and neither is a copy of the other.
643
+ */
644
+ const duplicateSequenceFlow = {
645
+ id: 'bpmn.duplicate-sequence-flow',
646
+ framework: 'bpmn',
647
+ family: 'relation-endpoints',
648
+ severity: 'warning',
649
+ roles: BPMN_ROLES,
650
+ messageKey: 'com.labre.bpmn.validation.duplicate-sequence-flow',
651
+ messageFallback: 'This sequence flow is already drawn between these two steps.',
652
+ suggestionKey: 'com.labre.bpmn.validation.duplicate-sequence-flow.suggestion',
653
+ suggestionFallback: 'Delete the copy. Two routes to the same step leave from different places — draw the second one out of a gateway, so the diagram says what decides between them.',
654
+ version: 1,
655
+ // An industry rule with a normative-LOOKING level in every linter a BPMN user
656
+ // has met, and no normative sentence behind it: bpmnlint raises it at ERROR,
657
+ // the specification does not forbid the shape.
658
+ provenance: {
659
+ source: 'recommendation',
660
+ reference: 'bpmnlint no-duplicate-sequence-flows (raised at ERROR there)',
661
+ },
662
+ backgroundRole: BPMN_ROLE.pool,
663
+ endpoints: {
664
+ edgeRole: BPMN_ROLE.sequenceFlow,
665
+ // The alphabet again, and only the alphabet: the matrix cannot fire here
666
+ // (B1 owns the sentence) and is present so that a flow onto an annotation,
667
+ // a document or a pool stays outside the conversation.
668
+ allowed: BPMN_SEQUENCE_MATRIX,
669
+ forbidDuplicate: true,
670
+ duplicate: {
671
+ messageKey: 'com.labre.bpmn.validation.duplicate-sequence-flow.copy',
672
+ messageFallback: 'These two steps are already joined by a sequence flow going the same way.',
673
+ suggestionKey: 'com.labre.bpmn.validation.duplicate-sequence-flow.copy.suggestion',
674
+ suggestionFallback: 'The second arrow adds nothing a reader can act on. Delete it — or, if the two routes are genuinely different, send one of them through a gateway.',
675
+ },
676
+ },
677
+ };
678
+ /**
679
+ * **B6** — a start event is where the process wakes up.
680
+ *
681
+ * Nothing flows INTO it: "a Start Event MUST NOT be a target of a Sequence Flow"
682
+ * (p.244). A start event with an incoming sequence flow is one of two mistakes
683
+ * and the user knows which — either the arrow is drawn backwards, or the symbol
684
+ * is not a start at all and wanted to be an intermediate event, which the
685
+ * descriptive profile does not draw, so it wanted to be a task.
686
+ *
687
+ * `maxIn: 0` and nothing else, so the two halves of "a start event begins the
688
+ * process" stay two rules with two sentences and two gestures to fix them — see
689
+ * {@link startEventMustExit}.
690
+ *
691
+ * Written on `bpmn:start-event`, so it covers the message and timer starts for
692
+ * free: "the process wakes up here" is true of all three, and a rule about where
693
+ * a process begins must never have to enumerate the triggers.
694
+ */
695
+ const startEventNoInflow = {
696
+ id: 'bpmn.start-event-no-inflow',
697
+ framework: 'bpmn',
698
+ family: 'edge-degree',
699
+ severity: 'warning',
700
+ appliesTo: BPMN_ROLE.startEvent,
701
+ roles: BPMN_ROLES,
702
+ messageKey: 'com.labre.bpmn.validation.start-event-no-inflow',
703
+ messageFallback: 'A sequence flow arrives at this start event.',
704
+ suggestionKey: 'com.labre.bpmn.validation.start-event-no-inflow.suggestion',
705
+ suggestionFallback: 'A start event is where the process wakes up — nothing flows into it. Reverse the arrow, or make this step a task if something really does happen before it.',
706
+ version: 1,
707
+ provenance: {
708
+ source: 'standard',
709
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.244 — a Start Event MUST NOT be a target of a Sequence Flow',
710
+ },
711
+ // Attribution only, so an arbitration made on one participant covers that
712
+ // participant. The count itself reads no geometry whatsoever.
713
+ backgroundRole: BPMN_ROLE.pool,
714
+ degree: {
715
+ edgeRole: BPMN_ROLE.sequenceFlow,
716
+ maxIn: 0,
717
+ },
718
+ };
719
+ /**
720
+ * **B7** — and something happens after it.
721
+ *
722
+ * "A Start Event MUST be a source of a Sequence Flow" (p.244). A start event with
723
+ * nothing leaving it is a process that wakes up and does nothing — and, read
724
+ * with B13, it is also the commonest cause of a whole pool being reported
725
+ * unreachable, since the traversal leaves from exactly here.
726
+ *
727
+ * The mirror of B6 and a separate rule rather than a second bound on it,
728
+ * deliberately: the two are fixed by opposite gestures, and one bracket saying
729
+ * both would leave the user to work out which half they got wrong on a symbol
730
+ * forty units across.
731
+ */
732
+ const startEventMustExit = {
733
+ id: 'bpmn.start-event-must-exit',
734
+ framework: 'bpmn',
735
+ family: 'edge-degree',
736
+ severity: 'warning',
737
+ appliesTo: BPMN_ROLE.startEvent,
738
+ roles: BPMN_ROLES,
739
+ messageKey: 'com.labre.bpmn.validation.start-event-must-exit',
740
+ messageFallback: 'Nothing follows this start event.',
741
+ suggestionKey: 'com.labre.bpmn.validation.start-event-must-exit.suggestion',
742
+ suggestionFallback: 'Draw the sequence flow out of it to the first thing the process does — until then the diagram says the participant wakes up and stops.',
743
+ version: 1,
744
+ provenance: {
745
+ source: 'standard',
746
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.244 — a Start Event MUST be a source of a Sequence Flow',
747
+ },
748
+ backgroundRole: BPMN_ROLE.pool,
749
+ degree: {
750
+ edgeRole: BPMN_ROLE.sequenceFlow,
751
+ minOut: 1,
752
+ },
753
+ };
754
+ /**
755
+ * **B8** — an end event is where the process stops.
756
+ *
757
+ * "An End Event MUST NOT be a source of a Sequence Flow" (p.248). The mirror of
758
+ * B6, and the same two mistakes read the other way: an arrow drawn backwards, or
759
+ * a symbol that meant to be a step. Nothing leaves an end event, because there is
760
+ * no instance left to carry the token.
761
+ *
762
+ * Written on `bpmn:end-event`, so the message and terminate ends are covered — a
763
+ * terminate end is even more final than a plain one, and if anything the rule
764
+ * matters more there.
765
+ */
766
+ const endEventNoOutflow = {
767
+ id: 'bpmn.end-event-no-outflow',
768
+ framework: 'bpmn',
769
+ family: 'edge-degree',
770
+ severity: 'warning',
771
+ appliesTo: BPMN_ROLE.endEvent,
772
+ roles: BPMN_ROLES,
773
+ messageKey: 'com.labre.bpmn.validation.end-event-no-outflow',
774
+ messageFallback: 'A sequence flow leaves this end event.',
775
+ suggestionKey: 'com.labre.bpmn.validation.end-event-no-outflow.suggestion',
776
+ suggestionFallback: 'An end event is where the process stops — nothing leaves it. Reverse the arrow, or make this step a task if the process really does carry on.',
777
+ version: 1,
778
+ provenance: {
779
+ source: 'standard',
780
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.248 — an End Event MUST NOT be a source of a Sequence Flow',
781
+ },
782
+ backgroundRole: BPMN_ROLE.pool,
783
+ degree: {
784
+ edgeRole: BPMN_ROLE.sequenceFlow,
785
+ maxOut: 0,
786
+ },
787
+ };
788
+ /**
789
+ * **B9** — and something leads to it.
790
+ *
791
+ * "An End Event MUST be a target of a Sequence Flow" (p.248). An end event
792
+ * nothing reaches is an outcome the process can never produce: the reader is
793
+ * shown a result the diagram has no path to.
794
+ *
795
+ * The mirror of B7, separate from B8 for the reason B7 is separate from B6.
796
+ */
797
+ const endEventMustBeReached = {
798
+ id: 'bpmn.end-event-must-be-reached',
799
+ framework: 'bpmn',
800
+ family: 'edge-degree',
801
+ severity: 'warning',
802
+ appliesTo: BPMN_ROLE.endEvent,
803
+ roles: BPMN_ROLES,
804
+ messageKey: 'com.labre.bpmn.validation.end-event-must-be-reached',
805
+ messageFallback: 'Nothing leads to this end event.',
806
+ suggestionKey: 'com.labre.bpmn.validation.end-event-must-be-reached.suggestion',
807
+ suggestionFallback: 'Draw the sequence flow into it from the last step of that path — an outcome nothing reaches is an outcome the process cannot produce.',
808
+ version: 1,
809
+ provenance: {
810
+ source: 'standard',
811
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.248 — an End Event MUST be a target of a Sequence Flow',
812
+ },
813
+ backgroundRole: BPMN_ROLE.pool,
814
+ degree: {
815
+ edgeRole: BPMN_ROLE.sequenceFlow,
816
+ minIn: 1,
817
+ },
818
+ };
819
+ /**
820
+ * **B10** — a step that leads nowhere, when the diagram ends elsewhere.
821
+ *
822
+ * `audit`, and the only rule in this file declared so on its own. The
823
+ * specification is explicit that an Activity with no outgoing Sequence Flow is a
824
+ * legitimate way to end a path (p.151): the token simply ends there, and a
825
+ * modelling STYLE that leaves the end events implicit is conformant BPMN.
826
+ *
827
+ * So this is not a conformance finding, and reporting it as one would be the tool
828
+ * arguing with a house style the spec sanctions. It stays because on a
829
+ * DESCRIPTIVE diagram — one drawn to be read by somebody who was not in the room
830
+ * — the implicit ending is almost always an omission rather than a style: the
831
+ * author drew end events for the other paths and forgot this one, and the reader
832
+ * cannot tell "we are done" from "the rest is missing".
833
+ *
834
+ * `audit` is exactly that distinction: the finding reaches `violations$` for the
835
+ * conformance panel and a report, and the canvas says nothing. `bpmn.descriptive`
836
+ * leaves it there, which is the whole reason a profile spells every severity out.
837
+ *
838
+ * `minOut: 1`, on `bpmn:activity`, so it covers tasks, user and service tasks,
839
+ * sub-processes and call activities alike.
840
+ */
841
+ const activityDeadEnd = {
842
+ id: 'bpmn.activity-dead-end',
843
+ framework: 'bpmn',
844
+ family: 'edge-degree',
845
+ // See the header: the spec sanctions the shape (p.151), so this is a nuance
846
+ // for the panel and never a warning on the canvas.
847
+ severity: 'audit',
848
+ appliesTo: BPMN_ROLE.activity,
849
+ roles: BPMN_ROLES,
850
+ messageKey: 'com.labre.bpmn.validation.activity-dead-end',
851
+ messageFallback: 'Nothing follows this step, so the path simply ends here.',
852
+ suggestionKey: 'com.labre.bpmn.validation.activity-dead-end.suggestion',
853
+ suggestionFallback: 'BPMN allows a path to end at a step. With end events in play elsewhere on the diagram, a step that leads nowhere is usually an omission — draw the flow on, or add the end event that says this outcome was reached.',
854
+ version: 1,
855
+ // NOT `standard`, and the page is cited for the opposite reason from
856
+ // everywhere else in this file: p.151 SANCTIONS the shape. The nudge is ours,
857
+ // so the citation names what the specification permits rather than what it
858
+ // requires — an architect must never read this as a conformance defect.
859
+ provenance: {
860
+ source: 'recommendation',
861
+ reference: 'Descriptive-reading nudge — OMG BPMN 2.0.2 p.151 expressly sanctions a path ending at an Activity',
862
+ },
863
+ backgroundRole: BPMN_ROLE.pool,
864
+ degree: {
865
+ edgeRole: BPMN_ROLE.sequenceFlow,
866
+ minOut: 1,
867
+ },
868
+ };
869
+ /**
870
+ * **B11** — a sequence flow stays inside its pool.
871
+ *
872
+ * Half of what a swimlane diagram MEANS, and normative: "…nor can Sequence
873
+ * Flows cross a Pool boundary" (p.40, restated p.502). A pool is one participant and a sequence
874
+ * flow is one participant's own order of work: a token never crosses from one
875
+ * participant to another, because a participant cannot run another's steps. What
876
+ * crosses is a message, and B12 is its half.
877
+ *
878
+ * Neither half is expressible by the grammar rules above, and that is the whole
879
+ * reason `edge-locality` exists: the two ends carry exactly the same roles in the
880
+ * legal case and the illegal one — task to task, either way — and the only thing
881
+ * that differs is which pool each of them is drawn on.
882
+ *
883
+ * ## What it needs the pool DECLARATION for
884
+ *
885
+ * "Inside the pool" is read against the pool's PLOT, not its element box, and the
886
+ * plot is where the declaration says the flow area starts — the left margin is
887
+ * the participant name band, and a task lying on the band is not in the flow area
888
+ * at all. Carrying `BPMN_POOL_BACKGROUND` here is what makes this rule and
889
+ * `bpmnPoolOf` give one answer to "which pool is this on" instead of two.
890
+ *
891
+ * ## Silence
892
+ *
893
+ * Total, when either end sits inside NO pool — a step dropped beside the frame is
894
+ * a draft, and a tool answering "that flow leaves the pool" would be indicting
895
+ * the act of sketching. Total, on a board carrying no pool at all: a process
896
+ * sketched before anybody drew a participant is a process, and this is the rule
897
+ * that would otherwise light it up from end to end.
898
+ */
899
+ const sequenceFlowStaysHome = {
900
+ id: 'bpmn.sequence-flow-stays-home',
901
+ framework: 'bpmn',
902
+ family: 'edge-locality',
903
+ severity: 'warning',
904
+ roles: BPMN_ROLES,
905
+ messageKey: 'com.labre.bpmn.validation.sequence-flow-stays-home',
906
+ messageFallback: 'This sequence flow crosses from one pool into another.',
907
+ suggestionKey: 'com.labre.bpmn.validation.sequence-flow-stays-home.suggestion',
908
+ suggestionFallback: 'A sequence flow chains steps of the same participant — between pools, send a message flow instead, or move the step into the pool that performs it.',
909
+ version: 1,
910
+ provenance: {
911
+ source: 'standard',
912
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.40, restated p.502 — Sequence Flows cannot cross a Pool boundary',
913
+ },
914
+ backgroundRole: BPMN_ROLE.pool,
915
+ // The declaration, so "inside the pool" means inside its FLOW AREA — the
916
+ // participant name band is not part of it. Same plot `bpmnPoolOf` reads.
917
+ background: BPMN_POOL_BACKGROUND,
918
+ locality: {
919
+ edgeRole: BPMN_ROLE.sequenceFlow,
920
+ mode: 'same-background',
921
+ },
922
+ };
923
+ /**
924
+ * **B12** — a message flow runs BETWEEN two pools.
925
+ *
926
+ * The other half, and normative too: "a Message Flow MUST connect two separate
927
+ * Pools" (p.119). A message inside one participant is a participant talking to
928
+ * itself: whatever the author meant, they meant a sequence flow — the two steps
929
+ * are in one process and one of them follows the other.
930
+ *
931
+ * The exact mirror of B11, one word apart in the declaration, and the pair is
932
+ * the reason the family is worth having at all.
933
+ *
934
+ * ## One deliberate divergence from p.119
935
+ *
936
+ * The spec's MUST also condemns a message flow with an end OUTSIDE any pool —
937
+ * there are not two pools to connect if there is only one. This rule stays
938
+ * SILENT there, exactly as B11 does, and the reason is the platform's own
939
+ * principle rather than a gap: the sketch primes (PRD principle 3). A message
940
+ * drawn to a task somebody has not yet dragged into its participant is the
941
+ * commonest intermediate state of a collaboration being built, and a tool
942
+ * arguing with it is a tool switched off before the second pool exists.
943
+ */
944
+ const messageFlowCrossesPools = {
945
+ id: 'bpmn.message-flow-crosses-pools',
946
+ framework: 'bpmn',
947
+ family: 'edge-locality',
948
+ severity: 'warning',
949
+ roles: BPMN_ROLES,
950
+ messageKey: 'com.labre.bpmn.validation.message-flow-crosses-pools',
951
+ messageFallback: 'This message flow stays inside one pool.',
952
+ suggestionKey: 'com.labre.bpmn.validation.message-flow-crosses-pools.suggestion',
953
+ suggestionFallback: 'A message is what one participant sends to another — inside a single pool, the two steps belong to one process, so draw a sequence flow instead.',
954
+ version: 1,
955
+ // Standard, with one deliberate SOFTENING recorded in the comment above: the
956
+ // MUST also condemns an end outside any pool, and we stay silent there. The
957
+ // provenance describes the authority the rule DOES exercise; the divergence
958
+ // is quieter than the norm, never louder.
959
+ provenance: {
960
+ source: 'standard',
961
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.119 — a Message Flow MUST connect two separate Pools',
962
+ },
963
+ backgroundRole: BPMN_ROLE.pool,
964
+ background: BPMN_POOL_BACKGROUND,
965
+ locality: {
966
+ edgeRole: BPMN_ROLE.messageFlow,
967
+ mode: 'cross-background',
968
+ },
969
+ };
970
+ /**
971
+ * **B13** — every step can be reached from the start.
972
+ *
973
+ * The orphan question, and the one no amount of looking at a symbol or at an
974
+ * arrow can answer: a task with a perfectly good sequence flow on either side is
975
+ * still unreachable if the chain it belongs to never starts anywhere. Only the
976
+ * graph knows — which is why this is the one rule here whose family builds one.
977
+ *
978
+ * The traversal leaves from every start event at once and follows sequence flows
979
+ * FORWARD only, source to target. Forward-only is the point: "reachable from the
980
+ * start" is a statement about the direction the process runs in, and a step that
981
+ * merely points BACK at the chain is exactly the mistake this exists to find.
982
+ *
983
+ * The subject is `bpmn:flow-object` — a stranded gateway is as much of a hole as
984
+ * a stranded task, and an end event nothing reaches is an outcome that can never
985
+ * happen. A root is never its own orphan: the roots are seeded into the traversal
986
+ * before it begins.
987
+ *
988
+ * ## `implicitRoots`, and the spec-legal diagram it protects
989
+ *
990
+ * A Process is NOT required to contain a Start Event (p.238), and p.245 says
991
+ * what happens then: "When a Start Event is not used, then all Flow Objects that
992
+ * do not have an incoming Sequence Flow SHALL be the start of a separate
993
+ * parallel path."
994
+ *
995
+ * ## The mixed case is OUR widening, not the spec's
996
+ *
997
+ * p.245's clause is CONDITIONAL — it speaks about a process with no start event
998
+ * at all — and the engine seeds in-degree-zero subjects as roots
999
+ * unconditionally. So a pool holding one explicit start PLUS a dangling branch
1000
+ * stays silent, where p.245 does not obviously sanction it. That is a deliberate
1001
+ * widening on our side, and it is the quieter direction: the alternative lights
1002
+ * up a whole branch of a diagram somebody is still drawing, because one marker
1003
+ * elsewhere on the board lifted the zero-root gate. The sketch primes (PRD
1004
+ * principle 3). What survives either way is the only real defect — a ring nothing
1005
+ * enters, where every step is pointed at and no walk reaches any of them.
1006
+ *
1007
+ * ## `on-demand`, and why that is a property of the rule
1008
+ *
1009
+ * A graph sweep is O(V + E) and rebuilds its adjacency from scratch on every
1010
+ * evaluation, and unlike every other rule in this file it cannot be made
1011
+ * incremental even in principle: reachability is a GLOBAL property, so
1012
+ * re-pointing one arrow can orphan or rescue an arbitrary number of steps nowhere
1013
+ * near it. So it stays OUT of the drawing budget entirely — `evaluateRules` skips
1014
+ * it before touching a single element, and it runs only when a user asks for a
1015
+ * check-up (`evaluateCheckup`). Declared once here, so no evaluation path has to
1016
+ * remember.
1017
+ *
1018
+ * It is also the honest moment for what the rule says. "Nothing reaches this
1019
+ * step" is true of every step for the first thirty seconds of drawing a process,
1020
+ * and a finding that is true-and-useless while a diagram is being built belongs
1021
+ * to the moment somebody asks whether the diagram is finished.
1022
+ */
1023
+ const unreachableStep = {
1024
+ id: 'bpmn.unreachable-step',
1025
+ framework: 'bpmn',
1026
+ family: 'reachability',
1027
+ severity: 'warning',
1028
+ roles: BPMN_ROLES,
1029
+ messageKey: 'com.labre.bpmn.validation.unreachable-step',
1030
+ messageFallback: 'Nothing leads to this step from any start event.',
1031
+ suggestionKey: 'com.labre.bpmn.validation.unreachable-step.suggestion',
1032
+ suggestionFallback: 'Follow the sequence flows back from here: somewhere the chain stops, or an arrow points the wrong way. Join it to the process, or delete it if it no longer happens.',
1033
+ version: 1,
1034
+ // The specification governs how the roots are seeded (p.238 / p.245, which is
1035
+ // what `implicitRoots` implements) and requires no reachability of anybody.
1036
+ // Flagging what the walk never reaches is best practice, not conformance.
1037
+ provenance: {
1038
+ source: 'recommendation',
1039
+ reference: 'Best practice — OMG BPMN 2.0.2 p.238 / p.245 govern the implicit roots; the specification requires no reachability',
1040
+ },
1041
+ // The graph sweep stays out of the 16 ms the drawing has: the family cannot be
1042
+ // made incremental, and the question is one somebody asks about a finished
1043
+ // diagram rather than one the tool asks while they draw. See the header.
1044
+ moment: 'on-demand',
1045
+ backgroundRole: BPMN_ROLE.pool,
1046
+ reachability: {
1047
+ rootRole: BPMN_ROLE.startEvent,
1048
+ subjectRole: BPMN_ROLE.flowObject,
1049
+ edgeRole: BPMN_ROLE.sequenceFlow,
1050
+ // p.238 / p.245 — see the header. A step nothing points at IS a beginning,
1051
+ // whether or not anybody drew the circle.
1052
+ implicitRoots: true,
1053
+ },
1054
+ };
1055
+ /**
1056
+ * **B14** — a process that says where it ends says where it begins.
1057
+ *
1058
+ * A verbatim normative MUST, guard included:
1059
+ *
1060
+ * > "If there is an End Event, then there MUST be at least one Start Event."
1061
+ * > — p.238
1062
+ *
1063
+ * The conditional is the SPEC'S, not a reading of ours, and that is the whole
1064
+ * shape of the rule: a pool needs NEITHER event — a Process is not required to
1065
+ * contain a Start Event (p.238), an End Event is optional (p.246), and a
1066
+ * black-box participant in a collaboration is conformant with nothing drawn
1067
+ * inside it at all. An unconditional "every pool holds a start event" fires on
1068
+ * all three, which is the tool inventing a requirement the notation does not
1069
+ * have; it is what took the naive version of this rule out of the pack.
1070
+ *
1071
+ * So the bound is conditional exactly as the sentence is: at least one start
1072
+ * event, **and only when the pool already holds an end event**
1073
+ * ({@link RoleCountDef.ifPresent}). What it protects the reader from is a
1074
+ * diagram that draws its outcome and not its trigger — half a model, and
1075
+ * unreadable as to what has to happen for that outcome to be produced.
1076
+ *
1077
+ * One of the four rules that would sit at `blocking-overridable` the day a
1078
+ * gesture refusal lands (see `profiles.ts`): the spec states it, and there is no
1079
+ * reading under which the author is right.
1080
+ *
1081
+ * ## Why the finding lands on the POOL
1082
+ *
1083
+ * No element is at fault — there is no start event to point at, which is the
1084
+ * whole finding — so `role-count` raises it ON the frame. That is what puts the
1085
+ * bracket on the pool, and what makes an arbitration made on one participant
1086
+ * cover that participant and no other on the board.
1087
+ *
1088
+ * No maximum: BPMN allows several starts in one pool (a process woken by a
1089
+ * message OR by a timer), and a descriptive diagram that draws both is saying
1090
+ * something true. Counted by CONTAINMENT and only containment: a start event
1091
+ * floating just outside the frame does not satisfy this, because membership has
1092
+ * to mean membership or the rule certifies a drawing that shows the opposite of
1093
+ * what it claims.
1094
+ */
1095
+ const poolEndWithoutStart = {
1096
+ id: 'bpmn.pool-end-without-start',
1097
+ framework: 'bpmn',
1098
+ family: 'role-count',
1099
+ severity: 'warning',
1100
+ roles: BPMN_ROLES,
1101
+ messageKey: 'com.labre.bpmn.validation.pool-end-without-start',
1102
+ messageFallback: 'This pool says where its process ends, and not where it begins.',
1103
+ suggestionKey: 'com.labre.bpmn.validation.pool-end-without-start.suggestion',
1104
+ suggestionFallback: 'Draw the start event that wakes this participant up — a message start if somebody else triggers it, a timer start if the clock does. A pool with no events at all is fine; one with only an ending is half a model.',
1105
+ version: 1,
1106
+ provenance: {
1107
+ source: 'standard',
1108
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.238 — "If there is an End Event, then there MUST be at least one Start Event"',
1109
+ },
1110
+ backgroundRole: BPMN_ROLE.pool,
1111
+ background: BPMN_POOL_BACKGROUND,
1112
+ roleCount: {
1113
+ // Matched by `roleIsA`, so the message and timer starts count. "One start
1114
+ // event" stays one requirement as the vocabulary grows.
1115
+ subject: BPMN_ROLE.startEvent,
1116
+ min: 1,
1117
+ // The guard, and the whole rule: a pool holding no end event is not judged
1118
+ // at all. p.238 / p.246.
1119
+ ifPresent: BPMN_ROLE.endEvent,
1120
+ },
1121
+ };
1122
+ /**
1123
+ * **B15** — and a process that says where it begins says where it ends.
1124
+ *
1125
+ * The mirror, and a verbatim MUST of its own:
1126
+ *
1127
+ * > "If there is a Start Event, then there MUST be at least one End Event."
1128
+ * > — p.246
1129
+ *
1130
+ * The one a reader misses more often: a pool whose process trails off has no
1131
+ * visible outcome, so nobody can say what "done" is for that participant.
1132
+ * Drawing the end event is how a diagram commits to one.
1133
+ *
1134
+ * Conditional in the same way and for the same reason: the requirement is the
1135
+ * pairing, so a pool holding no start event either is silent. Also on the
1136
+ * `blocking-overridable` list for the day refusal lands.
1137
+ * `min: 1` and no maximum — several ends is how a process says it has several
1138
+ * outcomes, and a descriptive diagram drawing "order shipped" and "order
1139
+ * cancelled" separately is saying something true.
1140
+ *
1141
+ * Raised on the pool, counted by containment, silent on a board with no pool.
1142
+ */
1143
+ const poolStartWithoutEnd = {
1144
+ id: 'bpmn.pool-start-without-end',
1145
+ framework: 'bpmn',
1146
+ family: 'role-count',
1147
+ severity: 'warning',
1148
+ roles: BPMN_ROLES,
1149
+ messageKey: 'com.labre.bpmn.validation.pool-start-without-end',
1150
+ messageFallback: 'This pool says where its process begins, and not where it ends.',
1151
+ suggestionKey: 'com.labre.bpmn.validation.pool-start-without-end.suggestion',
1152
+ suggestionFallback: 'Draw the end event that closes the process for this participant, so the reader can see what being done means here. Several outcomes take several end events.',
1153
+ version: 1,
1154
+ provenance: {
1155
+ source: 'standard',
1156
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.246 — "If there is a Start Event, then there MUST be at least one End Event"',
1157
+ },
1158
+ backgroundRole: BPMN_ROLE.pool,
1159
+ background: BPMN_POOL_BACKGROUND,
1160
+ roleCount: {
1161
+ subject: BPMN_ROLE.endEvent,
1162
+ min: 1,
1163
+ ifPresent: BPMN_ROLE.startEvent,
1164
+ },
1165
+ };
1166
+ /**
1167
+ * **B16** — a gateway that neither splits nor merges is not a gateway.
1168
+ *
1169
+ * "A Gateway MUST have either multiple incoming Sequence Flows or multiple
1170
+ * outgoing Sequence Flows" (p.289). A diamond with one arrow in and one arrow out
1171
+ * decides nothing and joins nothing: whatever the author had in mind, the diagram
1172
+ * shows a step, and drawing it as a decision tells the reader a question is being
1173
+ * asked that never is.
1174
+ *
1175
+ * Expressed as a FORBIDDEN ZONE rather than as a disjunction:
1176
+ * `{ maxIn: 1, maxOut: 1 }` is exactly the set of gateways that neither merge
1177
+ * nor split, and it needs no new operator — see
1178
+ * {@link EdgeDegreeDef.forbidPattern}. The four ordinary bounds the family has
1179
+ * today cannot say it: `minIn: 2` alone indicts every split, `minOut: 2` alone
1180
+ * indicts every merge, and both together indict everything.
1181
+ *
1182
+ * ## It absorbs the "superfluous gateway" nudge, because they are one predicate
1183
+ *
1184
+ * A diamond with one flow in and one flow out is simultaneously "does not
1185
+ * satisfy p.289" and "is doing nothing at all": the two descriptions have the
1186
+ * same extension, exactly. Shipping both would put two brackets and two
1187
+ * severities on one symbol for one gesture to fix. This rule keeps the
1188
+ * spec-backed reading and the higher severity of the two.
1189
+ *
1190
+ * ## The sentence covers TWO shapes, because the pattern does
1191
+ *
1192
+ * `maxIn: 1` includes `in == 0`, so the zone also contains the gateway somebody
1193
+ * has just dropped from the toolbar and not yet connected. An earlier wording
1194
+ * said "takes one flow in and puts one flow out", which is a claim about a
1195
+ * board that in that case has no such flows on it — data describing a shape the
1196
+ * diagram does not have, which is the one thing this file refuses everywhere
1197
+ * else. The message is therefore the PREDICATE — neither splits nor merges —
1198
+ * which is true of the 1/1 diamond and of the naked one alike, and the
1199
+ * suggestion names the three gestures that resolve either.
1200
+ *
1201
+ * `warning` and not louder, despite the MUST: p.288's instantiating parallel
1202
+ * gateway is a real, conformant exception — "If the Gateway does not have an
1203
+ * incoming Sequence Flow, and there is no Start Event for the Process, then the
1204
+ * Gateway's divergence behavior SHALL be performed when the Process is
1205
+ * instantiated" — so it has ZERO incoming flows, not one. The predicate copes
1206
+ * either way (such a gateway diverges, so `out >= 2` and `maxOut: 1` fails), but
1207
+ * the severity is set to what the rule can honestly claim about a shape it
1208
+ * cannot tell apart from a mistake.
1209
+ */
1210
+ const gatewayMustBranch = {
1211
+ id: 'bpmn.gateway-must-branch',
1212
+ framework: 'bpmn',
1213
+ family: 'edge-degree',
1214
+ severity: 'warning',
1215
+ appliesTo: BPMN_ROLE.gateway,
1216
+ roles: BPMN_ROLES,
1217
+ messageKey: 'com.labre.bpmn.validation.gateway-must-branch',
1218
+ messageFallback: 'This gateway neither splits nor merges.',
1219
+ suggestionKey: 'com.labre.bpmn.validation.gateway-must-branch.suggestion',
1220
+ suggestionFallback: 'A gateway is a fork or a join: draw the second branch out of it, bring the second path into it, or delete it and let the sequence flow run straight through.',
1221
+ version: 1,
1222
+ provenance: {
1223
+ source: 'standard',
1224
+ reference: 'OMG BPMN 2.0.2 (ISO/IEC 19510) p.289 — a Gateway MUST have either multiple incoming or multiple outgoing Sequence Flows',
1225
+ },
1226
+ backgroundRole: BPMN_ROLE.pool,
1227
+ degree: {
1228
+ edgeRole: BPMN_ROLE.sequenceFlow,
1229
+ // p.289, as a forbidden zone: at most one in AND at most one out is
1230
+ // exactly the set of gateways that neither merge nor split. The pattern
1231
+ // carries its own words, because a shape the diagram HAS never reads like
1232
+ // a bound it is missing.
1233
+ forbidPattern: {
1234
+ maxIn: 1,
1235
+ maxOut: 1,
1236
+ messageKey: 'com.labre.bpmn.validation.gateway-must-branch.idle',
1237
+ messageFallback: 'This gateway neither splits nor merges, so it decides nothing.',
1238
+ suggestionKey: 'com.labre.bpmn.validation.gateway-must-branch.idle.suggestion',
1239
+ suggestionFallback: 'Draw the second branch out of it, bring the second path into it, or delete it and let the sequence flow run straight through.',
1240
+ },
1241
+ },
1242
+ };
1243
+ /**
1244
+ * **B17** — a gateway that merges and splits at once.
1245
+ *
1246
+ * bpmnlint's `no-gateway-join-fork`, an ERROR there, and a real ambiguity rather
1247
+ * than a style point: a diamond with two flows in and two flows out gives the
1248
+ * reader no way to know whether it waits for both branches before deciding, or
1249
+ * decides on whichever arrives first. The two readings produce different
1250
+ * processes, and the notation for saying which is two gateways — a join, then a
1251
+ * fork — with a sequence flow between them.
1252
+ *
1253
+ * The forbidden zone is `{ minIn: 2, minOut: 2 }`, and both halves are ordinary
1254
+ * on their own: every merge satisfies the first, every split the second. Only
1255
+ * the conjunction is the mistake, which is precisely what
1256
+ * {@link EdgeDegreeDef.forbidPattern} exists for.
1257
+ */
1258
+ const gatewayJoinAndFork = {
1259
+ id: 'bpmn.gateway-join-and-fork',
1260
+ framework: 'bpmn',
1261
+ family: 'edge-degree',
1262
+ severity: 'warning',
1263
+ appliesTo: BPMN_ROLE.gateway,
1264
+ roles: BPMN_ROLES,
1265
+ messageKey: 'com.labre.bpmn.validation.gateway-join-and-fork',
1266
+ messageFallback: 'This gateway both merges paths and splits them.',
1267
+ suggestionKey: 'com.labre.bpmn.validation.gateway-join-and-fork.suggestion',
1268
+ suggestionFallback: 'A reader cannot tell whether it waits for the incoming paths or races them. Split it in two — a gateway that joins, a sequence flow, then a gateway that forks.',
1269
+ version: 1,
1270
+ // NOT p.289, which is B16's sentence and is satisfied here twice over — a
1271
+ // gateway with two in and two out both merges and splits. BPMN 2.0 permits
1272
+ // the mixed gateway; it is the READER who cannot resolve it. So the authority
1273
+ // is the linter's, and the citation says so rather than borrowing a page.
1274
+ provenance: {
1275
+ source: 'recommendation',
1276
+ reference: 'bpmnlint no-gateway-join-fork (raised at ERROR there)',
1277
+ },
1278
+ backgroundRole: BPMN_ROLE.pool,
1279
+ degree: {
1280
+ edgeRole: BPMN_ROLE.sequenceFlow,
1281
+ forbidPattern: {
1282
+ minIn: 2,
1283
+ minOut: 2,
1284
+ messageKey: 'com.labre.bpmn.validation.gateway-join-and-fork.both',
1285
+ messageFallback: 'This gateway both merges paths and splits them.',
1286
+ suggestionKey: 'com.labre.bpmn.validation.gateway-join-and-fork.both.suggestion',
1287
+ suggestionFallback: 'A reader cannot tell whether it waits for the incoming paths or races them. Split it in two — a gateway that joins, a sequence flow, then a gateway that forks.',
1288
+ },
1289
+ },
1290
+ };
1291
+ /**
1292
+ * **B18** — several paths arriving at one step, with no gateway to join them.
1293
+ *
1294
+ * The uncontrolled merge. `audit`, and deliberately quieter than bpmnlint's own
1295
+ * warning, because OMG p.151 is explicit that an activity may have multiple
1296
+ * incoming Sequence Flows and that the token semantics are defined for it: the
1297
+ * activity simply runs once per token that arrives. It is conformant BPMN and a
1298
+ * common, readable shorthand.
1299
+ *
1300
+ * What it costs a reader is the QUESTION the diagram no longer asks out loud:
1301
+ * does this step wait for the other paths, or run twice? A joining gateway
1302
+ * answers it in one symbol. So the finding is a nuance for the conformance
1303
+ * panel — never a bracket on the canvas — exactly like {@link activityDeadEnd},
1304
+ * and for the same reason.
1305
+ *
1306
+ * ## Activities only, and why the events are deliberately out
1307
+ *
1308
+ * On this vocabulary, applying it to `bpmn:event` would add exactly one
1309
+ * behaviour: flagging an END EVENT that several paths converge on. That is not a
1310
+ * shorthand, it is the notation working — one outcome reached by several routes
1311
+ * is precisely what an end event is for, and every descriptive diagram in the
1312
+ * corpus draws one. A start event cannot have an incoming flow at all (B6), so
1313
+ * there is nothing left for the wider role to catch.
1314
+ */
1315
+ const fakeJoin = {
1316
+ id: 'bpmn.fake-join',
1317
+ framework: 'bpmn',
1318
+ family: 'edge-degree',
1319
+ severity: 'audit',
1320
+ appliesTo: BPMN_ROLE.activity,
1321
+ roles: BPMN_ROLES,
1322
+ messageKey: 'com.labre.bpmn.validation.fake-join',
1323
+ messageFallback: 'Several paths arrive at this step without a gateway.',
1324
+ suggestionKey: 'com.labre.bpmn.validation.fake-join.suggestion',
1325
+ suggestionFallback: 'BPMN allows it, and the step runs once per path that reaches it. If it is meant to WAIT for the others instead, bring the paths into a joining gateway and let one flow out of it.',
1326
+ version: 1,
1327
+ provenance: {
1328
+ source: 'recommendation',
1329
+ reference: 'Industry linter practice (bpmnlint), softened — OMG BPMN 2.0.2 p.151 defines the token semantics and sanctions the shape',
1330
+ },
1331
+ backgroundRole: BPMN_ROLE.pool,
1332
+ degree: {
1333
+ edgeRole: BPMN_ROLE.sequenceFlow,
1334
+ forbidPattern: {
1335
+ minIn: 2,
1336
+ messageKey: 'com.labre.bpmn.validation.fake-join.merge',
1337
+ messageFallback: 'Several paths arrive at this step without a gateway.',
1338
+ suggestionKey: 'com.labre.bpmn.validation.fake-join.merge.suggestion',
1339
+ suggestionFallback: 'BPMN allows it, and the step runs once per path that reaches it. If it is meant to WAIT for the others instead, bring the paths into a joining gateway and let one flow out of it.',
1340
+ },
1341
+ },
1342
+ };
1343
+ /**
1344
+ * **B19** — several paths leaving one step, with no gateway to split them.
1345
+ *
1346
+ * The mirror: an implicit parallel split. Two arrows out of a task means both
1347
+ * branches run, every time — which is a legitimate thing to mean, and an
1348
+ * invisible one, because nothing on the diagram distinguishes it from a decision
1349
+ * somebody forgot to draw.
1350
+ *
1351
+ * `audit`, and OURS is stricter than bpmnlint's for a reason worth stating: on a
1352
+ * fully modelled diagram the ambiguity is resolved by CONDITIONS on the outgoing
1353
+ * flows, and the descriptive profile models none — we have no conditional
1354
+ * sequence flow, so the reader has nothing to disambiguate with. We stay quiet
1355
+ * outside the panel precisely because the gap is ours and not the author's.
1356
+ *
1357
+ * Activities only, for {@link fakeJoin}'s reasons read the other way: an end
1358
+ * event cannot have an outgoing flow at all (B8), and a start event with two
1359
+ * would be the same nuance on a symbol nobody splits from.
1360
+ */
1361
+ const implicitSplit = {
1362
+ id: 'bpmn.implicit-split',
1363
+ framework: 'bpmn',
1364
+ family: 'edge-degree',
1365
+ severity: 'audit',
1366
+ appliesTo: BPMN_ROLE.activity,
1367
+ roles: BPMN_ROLES,
1368
+ messageKey: 'com.labre.bpmn.validation.implicit-split',
1369
+ messageFallback: 'Several paths leave this step without a gateway.',
1370
+ suggestionKey: 'com.labre.bpmn.validation.implicit-split.suggestion',
1371
+ suggestionFallback: 'As drawn, every path runs. If the process chooses between them, put an exclusive gateway after the step and name what it decides on.',
1372
+ version: 1,
1373
+ provenance: {
1374
+ source: 'recommendation',
1375
+ reference: 'bpmnlint no-implicit-split, made stricter — OMG BPMN 2.0.2 p.151 sanctions the shape',
1376
+ },
1377
+ backgroundRole: BPMN_ROLE.pool,
1378
+ degree: {
1379
+ edgeRole: BPMN_ROLE.sequenceFlow,
1380
+ forbidPattern: {
1381
+ minOut: 2,
1382
+ messageKey: 'com.labre.bpmn.validation.implicit-split.fork',
1383
+ messageFallback: 'Several paths leave this step without a gateway.',
1384
+ suggestionKey: 'com.labre.bpmn.validation.implicit-split.fork.suggestion',
1385
+ suggestionFallback: 'As drawn, every path runs. If the process chooses between them, put an exclusive gateway after the step and name what it decides on.',
1386
+ },
1387
+ },
1388
+ };
1389
+ /**
1390
+ * **B20** — one BLANK start event per pool.
1391
+ *
1392
+ * bpmnlint's `single-blank-start-event`, an ERROR there. Several start events in
1393
+ * one pool is legal and useful — a process woken by a message OR by a timer says
1394
+ * so with two symbols — but only while the reader can tell them APART. Two blank
1395
+ * circles say "the process starts here" twice, with nothing to distinguish the
1396
+ * two occasions, so the diagram claims two triggers and names neither.
1397
+ *
1398
+ * ## Why it needs `exact`
1399
+ *
1400
+ * Every other existence rule here counts with `roleIsA` descent, which is what
1401
+ * keeps "a pool holds a start event" one requirement as the vocabulary grows.
1402
+ * This rule needs the opposite and cannot fake it: the typed starts are exactly
1403
+ * the ones that DO NOT count, because being typed is what makes them
1404
+ * distinguishable. `max: 1` with the descent on would indict the pool that draws
1405
+ * a message start beside a timer start — the one diagram this rule exists to
1406
+ * permit. See {@link RoleCountDef.exact}.
1407
+ *
1408
+ * `audit`: the diagram is ambiguous rather than wrong, and the fix is often to
1409
+ * TYPE one of the two starts, which is a modelling decision only the author can
1410
+ * make.
1411
+ */
1412
+ const singleBlankStart = {
1413
+ id: 'bpmn.single-blank-start',
1414
+ framework: 'bpmn',
1415
+ family: 'role-count',
1416
+ severity: 'audit',
1417
+ roles: BPMN_ROLES,
1418
+ messageKey: 'com.labre.bpmn.validation.single-blank-start',
1419
+ messageFallback: 'This pool holds more than one untyped start event.',
1420
+ suggestionKey: 'com.labre.bpmn.validation.single-blank-start.suggestion',
1421
+ suggestionFallback: 'Two plain circles say the process starts here twice, without saying on which two occasions. Give each start its trigger — a message start, a timer start — or keep one.',
1422
+ version: 1,
1423
+ // Several starts in one pool is LEGAL (p.238 permits them); the linter's rule
1424
+ // is about telling them apart, which is readability and not conformance.
1425
+ provenance: {
1426
+ source: 'recommendation',
1427
+ reference: 'bpmnlint single-blank-start-event (raised at ERROR there)',
1428
+ },
1429
+ backgroundRole: BPMN_ROLE.pool,
1430
+ background: BPMN_POOL_BACKGROUND,
1431
+ roleCount: {
1432
+ subject: BPMN_ROLE.startEvent,
1433
+ max: 1,
1434
+ // No `roleIsA` descent: the TYPED starts are exactly the ones that must not
1435
+ // count, because being typed is what makes them tellable apart.
1436
+ exact: true,
1437
+ },
1438
+ };
1439
+ /**
1440
+ * **B21** — a step nobody has named.
1441
+ *
1442
+ * bpmnlint's `label-required`, an ERROR there, and the softening is deliberate on
1443
+ * two axes at once.
1444
+ *
1445
+ * **`on-demand`**, because a task is created unnamed: the gesture that makes one
1446
+ * puts an empty rectangle on the canvas and the author types into it a second
1447
+ * later. A realtime rule would bracket every symbol the moment it appears, which
1448
+ * is not a validation platform, it is a tool arguing with the act of drawing
1449
+ * (PRD principle 3). "Three steps are unnamed" is a sentence for the check-up
1450
+ * panel, once, when somebody asks whether the diagram is finished.
1451
+ *
1452
+ * **`audit`**, because an unnamed step is a diagram that is not done rather than
1453
+ * a diagram that is wrong, and the panel is where "not done" belongs.
1454
+ *
1455
+ * Written on `bpmn:flow-object`: an unnamed gateway is worse than an unnamed
1456
+ * task, since the whole content of a decision is the question it asks.
1457
+ *
1458
+ * The `label-presence` family reads the subject's OWN `text`, which is where
1459
+ * BPMN puts the name of a step — so this rule can be asked at all.
1460
+ */
1461
+ const unlabeledStep = {
1462
+ id: 'bpmn.unlabeled-step',
1463
+ framework: 'bpmn',
1464
+ family: 'label-presence',
1465
+ severity: 'audit',
1466
+ appliesTo: BPMN_ROLE.flowObject,
1467
+ roles: BPMN_ROLES,
1468
+ messageKey: 'com.labre.bpmn.validation.unlabeled-step',
1469
+ messageFallback: 'This step has no name.',
1470
+ suggestionKey: 'com.labre.bpmn.validation.unlabeled-step.suggestion',
1471
+ suggestionFallback: 'Name it in a verb phrase a reader outside the room would understand — "Check the credit limit" rather than "Step 3". An unnamed gateway is worse still: the whole content of a decision is the question it asks.',
1472
+ version: 1,
1473
+ // A name is nowhere required by the specification: an unnamed step is a
1474
+ // diagram that is not done, never one that is wrong.
1475
+ provenance: {
1476
+ source: 'recommendation',
1477
+ reference: 'bpmnlint label-required, softened to an on-demand audit',
1478
+ },
1479
+ // Explicitly on-demand, and it stays explicit: `moment: undefined` means
1480
+ // REALTIME, and the engine watches `text` for exactly this family when a
1481
+ // real-time rule of it is registered. Dropping this line would hand the
1482
+ // drawing path a debounced re-evaluation per keystroke, which is the cost the
1483
+ // second moment exists to refuse.
1484
+ moment: 'on-demand',
1485
+ backgroundRole: BPMN_ROLE.pool,
1486
+ label: { present: true },
1487
+ };
1488
+ /**
1489
+ * The pack, whole: twenty-two rules, all registered, all live.
1490
+ *
1491
+ * Eight of them were authored ahead of the engine and sat in a held-out array
1492
+ * for one review cycle, because two would have been actively WRONG meanwhile —
1493
+ * an engine with no `ifPresent` reads B14/B15 as the unconditional "every pool
1494
+ * holds a start event" the specification review removed, which fires on a
1495
+ * conformant black-box pool. `claude/bpmn-engine-v2` (#145) landed every field
1496
+ * they asked for, so the array is gone and the split with it.
1497
+ *
1498
+ * The twenty-second is B1a, and it is not a new REQUIREMENT: the no-self-loop
1499
+ * clause left B1 so that each of the two could declare its own provenance
1500
+ * honestly. Nothing new fires, nothing new is said, and the two i18n keys are
1501
+ * the ones the clause already shipped.
1502
+ *
1503
+ * Every one of the twenty-two declares {@link ValidationRule.provenance}:
1504
+ * twelve `standard`, each with its page; eight `recommendation`, each naming a
1505
+ * linter or the sentence the specification merely permits; two
1506
+ * `labre-convention`. Nothing here is `organization` — that source is reserved
1507
+ * for the org profiles the PRD names, and no framework declares one yet.
1508
+ */
1509
+ export const BPMN_RULES = [
1510
+ // Connection: what a link may run between, and how many times.
1511
+ sequenceFlowEndpoints,
1512
+ sequenceFlowSelfLoop,
1513
+ messageFlowEndpoints,
1514
+ associationEndpoints,
1515
+ untypedFlow,
1516
+ duplicateSequenceFlow,
1517
+ // Degree: how many links reach one symbol, and which shapes of degree are
1518
+ // forbidden outright.
1519
+ startEventNoInflow,
1520
+ startEventMustExit,
1521
+ endEventNoOutflow,
1522
+ endEventMustBeReached,
1523
+ activityDeadEnd,
1524
+ gatewayMustBranch,
1525
+ gatewayJoinAndFork,
1526
+ fakeJoin,
1527
+ implicitSplit,
1528
+ // Locality: which pool each end sits in.
1529
+ sequenceFlowStaysHome,
1530
+ messageFlowCrossesPools,
1531
+ // Existence: what one pool must contain, and how many of it.
1532
+ poolEndWithoutStart,
1533
+ poolStartWithoutEnd,
1534
+ singleBlankStart,
1535
+ // Topology: what the graph as a whole says.
1536
+ unreachableStep,
1537
+ // Naming: whether the symbol says anything at all.
1538
+ unlabeledStep,
1539
+ ];