@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.
- package/dist/actions.d.ts +213 -0
- package/dist/actions.js +467 -0
- package/dist/background.d.ts +2 -0
- package/dist/background.js +158 -0
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +567 -0
- package/dist/consts.d.ts +157 -3
- package/dist/consts.js +192 -3
- package/dist/descriptor.d.ts +8 -3
- package/dist/descriptor.js +6 -3
- package/dist/element-renderer.d.ts +10 -4
- package/dist/element-renderer.js +14 -55
- package/dist/element-view.d.ts +100 -8
- package/dist/element-view.js +249 -30
- package/dist/export.d.ts +277 -0
- package/dist/export.js +1802 -0
- package/dist/facts.d.ts +48 -0
- package/dist/facts.js +127 -0
- package/dist/import.d.ts +44 -0
- package/dist/import.js +1440 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +46 -1
- package/dist/interchange.d.ts +109 -0
- package/dist/interchange.js +191 -0
- package/dist/morph.d.ts +61 -0
- package/dist/morph.js +118 -0
- package/dist/node/node-renderer.d.ts +0 -9
- package/dist/node/node-renderer.js +294 -17
- package/dist/pool-hit.d.ts +98 -0
- package/dist/pool-hit.js +130 -0
- package/dist/presets.d.ts +114 -0
- package/dist/presets.js +232 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +189 -0
- package/dist/roles.d.ts +96 -0
- package/dist/roles.js +410 -0
- package/dist/rules.d.ts +199 -0
- package/dist/rules.js +1539 -0
- package/dist/templates/index.js +116 -9
- package/dist/toolbar/bpmn-menu.d.ts +6 -21
- package/dist/toolbar/bpmn-menu.js +6 -173
- package/dist/toolbar/bpmn-senior-button.js +8 -2
- package/dist/toolbar/config.d.ts +27 -2
- package/dist/toolbar/config.js +86 -2
- package/dist/toolbar/icons.d.ts +67 -0
- package/dist/toolbar/icons.js +141 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/translations.d.ts +16 -0
- package/dist/translations.js +20 -0
- package/dist/view.d.ts +18 -0
- package/dist/view.js +95 -7
- 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
|
+
];
|