@formicoidea/labre-framework-uml 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +384 -0
  2. package/dist/actions.d.ts +430 -0
  3. package/dist/actions.js +724 -0
  4. package/dist/background.d.ts +153 -0
  5. package/dist/background.js +557 -0
  6. package/dist/board-hit.d.ts +137 -0
  7. package/dist/board-hit.js +191 -0
  8. package/dist/commands-manifest.d.ts +19 -0
  9. package/dist/commands-manifest.js +571 -0
  10. package/dist/commands.d.ts +4 -0
  11. package/dist/commands.js +983 -0
  12. package/dist/component.d.ts +521 -0
  13. package/dist/component.js +708 -0
  14. package/dist/consts.d.ts +365 -0
  15. package/dist/consts.js +463 -0
  16. package/dist/descriptor.d.ts +12 -0
  17. package/dist/descriptor.js +10 -0
  18. package/dist/drawio-decode.d.ts +32 -0
  19. package/dist/drawio-decode.js +96 -0
  20. package/dist/drawio-import.d.ts +133 -0
  21. package/dist/drawio-import.js +885 -0
  22. package/dist/edge-morph.d.ts +154 -0
  23. package/dist/edge-morph.js +153 -0
  24. package/dist/edge-styles.d.ts +78 -0
  25. package/dist/edge-styles.js +210 -0
  26. package/dist/effects.d.ts +9 -0
  27. package/dist/effects.js +6 -0
  28. package/dist/element-renderer.d.ts +64 -0
  29. package/dist/element-renderer.js +114 -0
  30. package/dist/element-view.d.ts +234 -0
  31. package/dist/element-view.js +430 -0
  32. package/dist/export.d.ts +61 -0
  33. package/dist/export.js +41 -0
  34. package/dist/filename.d.ts +17 -0
  35. package/dist/filename.js +26 -0
  36. package/dist/frame-tag.d.ts +70 -0
  37. package/dist/frame-tag.js +78 -0
  38. package/dist/grammar.d.ts +695 -0
  39. package/dist/grammar.js +1436 -0
  40. package/dist/import.d.ts +306 -0
  41. package/dist/import.js +1270 -0
  42. package/dist/index.d.ts +47 -0
  43. package/dist/index.js +67 -0
  44. package/dist/interchange.d.ts +125 -0
  45. package/dist/interchange.js +372 -0
  46. package/dist/keywords.d.ts +204 -0
  47. package/dist/keywords.js +361 -0
  48. package/dist/kinds.d.ts +73 -0
  49. package/dist/kinds.js +91 -0
  50. package/dist/legend.d.ts +30 -0
  51. package/dist/legend.js +255 -0
  52. package/dist/model.d.ts +862 -0
  53. package/dist/model.js +1316 -0
  54. package/dist/morph.d.ts +178 -0
  55. package/dist/morph.js +496 -0
  56. package/dist/node/act-glyphs.d.ts +30 -0
  57. package/dist/node/act-glyphs.js +142 -0
  58. package/dist/node/compartment-watcher.d.ts +107 -0
  59. package/dist/node/compartment-watcher.js +163 -0
  60. package/dist/node/fit.d.ts +85 -0
  61. package/dist/node/fit.js +129 -0
  62. package/dist/node/node-renderer.d.ts +49 -0
  63. package/dist/node/node-renderer.js +611 -0
  64. package/dist/node/node-view.d.ts +51 -0
  65. package/dist/node/node-view.js +89 -0
  66. package/dist/node/paint.d.ts +84 -0
  67. package/dist/node/paint.js +143 -0
  68. package/dist/node/sd-glyphs.d.ts +36 -0
  69. package/dist/node/sd-glyphs.js +79 -0
  70. package/dist/node/stm-glyphs.d.ts +22 -0
  71. package/dist/node/stm-glyphs.js +111 -0
  72. package/dist/node/tier-metrics.d.ts +38 -0
  73. package/dist/node/tier-metrics.js +45 -0
  74. package/dist/operators.d.ts +45 -0
  75. package/dist/operators.js +75 -0
  76. package/dist/plantuml-import.d.ts +113 -0
  77. package/dist/plantuml-import.js +1723 -0
  78. package/dist/plantuml.d.ts +94 -0
  79. package/dist/plantuml.js +985 -0
  80. package/dist/presets.d.ts +149 -0
  81. package/dist/presets.js +302 -0
  82. package/dist/profiles.d.ts +2 -0
  83. package/dist/profiles.js +358 -0
  84. package/dist/reading.d.ts +160 -0
  85. package/dist/reading.js +648 -0
  86. package/dist/roles.d.ts +257 -0
  87. package/dist/roles.js +997 -0
  88. package/dist/rules.d.ts +267 -0
  89. package/dist/rules.js +3090 -0
  90. package/dist/templates.d.ts +2 -0
  91. package/dist/templates.js +197 -0
  92. package/dist/toolbar/config.d.ts +228 -0
  93. package/dist/toolbar/config.js +833 -0
  94. package/dist/toolbar/icons.d.ts +440 -0
  95. package/dist/toolbar/icons.js +711 -0
  96. package/dist/toolbar/senior-tool.d.ts +11 -0
  97. package/dist/toolbar/senior-tool.js +21 -0
  98. package/dist/toolbar/uml-menu.d.ts +17 -0
  99. package/dist/toolbar/uml-menu.js +20 -0
  100. package/dist/toolbar/uml-senior-button.d.ts +18 -0
  101. package/dist/toolbar/uml-senior-button.js +22 -0
  102. package/dist/translations.d.ts +27 -0
  103. package/dist/translations.js +117 -0
  104. package/dist/view.d.ts +33 -0
  105. package/dist/view.js +236 -0
  106. package/dist/xmi-import.d.ts +174 -0
  107. package/dist/xmi-import.js +2268 -0
  108. package/dist/xmi.d.ts +61 -0
  109. package/dist/xmi.js +1860 -0
  110. package/dist/xml-reader.d.ts +157 -0
  111. package/dist/xml-reader.js +430 -0
  112. package/dist/xml.d.ts +72 -0
  113. package/dist/xml.js +96 -0
  114. package/package.json +38 -0
package/dist/rules.js ADDED
@@ -0,0 +1,3090 @@
1
+ import { UML_SUBJECT_FRAME } from './background.js';
2
+ import { UML_PORT_BORDER_TOLERANCE } from './consts.js';
3
+ import { checkEndLabelMultiplicity, checkLifelineIdent, checkMessageLabel, checkOperationLine, checkPropertyLine, checkTransitionLabel, } from './grammar.js';
4
+ import { UML_ROLE, UML_ROLES } from './roles.js';
5
+ /**
6
+ * UML validation rules — the notation's own grammar, as DATA.
7
+ *
8
+ * DATA owned by the framework and versioned per rule: the engine
9
+ * (`@labre/affine-block-surface`) knows how to evaluate a FAMILY, never a
10
+ * concrete rule, so adding a UML rule is adding an entry to the array at the
11
+ * bottom of this file. Registered from the flag-gated `UmlViewExtension`, so
12
+ * switching the `uml` flag off removes the rules with the rest of the tooling —
13
+ * diagrams already drawn keep rendering, they simply stop being checked
14
+ * (`docs/adr/0009`).
15
+ *
16
+ * ## Where the rules come from, which is unlike every pack before it
17
+ *
18
+ * UML has a SPECIFICATION. Where `c4/rules.ts` opens by explaining that its
19
+ * authority is a review checklist because there is nothing else to cite, this
20
+ * pack cites clauses: §9.9.7 says a generalization's two ends are of the same
21
+ * kind, §10.4.3 types an interface realization's contract, §11.5.3 gives a part
22
+ * at most one composite owner, §18.1.3 types the ends of an include and forbids
23
+ * an association between two actors, §19.2.3 types a deployment's two ends and
24
+ * §19.4.3 says a communication path joins two DeploymentTargets. The behaviour
25
+ * sheets cite seven more, and they are the arithmetic ones: §15.3.3 gives an
26
+ * initial node no incoming edge, §15.7.19.4 gives a final node no outgoing one,
27
+ * §15.7.11.4 caps a decision at two incoming edges, §15.7.9.4 keeps an object
28
+ * node off a control flow, §14.5.11.4 gives a final state no outgoing transition
29
+ * and §14.5.12 types a transition's two ends. The four SPELLING rules cite four
30
+ * more, and they are the only clauses in the pack quoted as productions rather
31
+ * than as sentences: §9.5.4 (a Property), §9.6.4 (an Operation), §14.2.4.8 (a
32
+ * transition label) and §7.5.4 (a multiplicity range). TWENTY-ONE rules
33
+ * therefore declare `provenance.source: 'standard'` — the most in this library
34
+ * by a distance — and they are the twenty-one a conformance report may present
35
+ * as defects.
36
+ *
37
+ * The rest are `recommendation` (a reading of the notation the spec draws but
38
+ * does not constrain: which artefacts belong on which KIND of sheet, whether an
39
+ * artefact is named at all) or `labre-convention` (membership on this canvas, and
40
+ * the role-less connector this whiteboard can produce and the notation never
41
+ * anticipated). Each says so in its own citation, so a reader who opens the
42
+ * bubble is never told UML forbids what UML does not.
43
+ *
44
+ * ## Severity: the CROQUIS primes, so everything is `audit`
45
+ *
46
+ * The same promise `wardley/rules.ts:30`, `bpmn/rules.ts` and `c4/rules.ts`
47
+ * make. Every rule here is declared `audit` and the default profile —
48
+ * `uml.sketch` — keeps it there. A class diagram is drawn boxes-first,
49
+ * arrows-second, words-last, and for the whole of that the drawing contradicts
50
+ * half this file. `uml.strict` is the level somebody chooses once the diagram is
51
+ * a deliverable, and it promotes the twenty-five rules that restate the
52
+ * specification
53
+ * or the sheet's own declaration (`profiles.ts`).
54
+ *
55
+ * `blocking-overridable` appears nowhere: nothing downstream implements a
56
+ * blocking level, so shipping the value would be data claiming an effect that
57
+ * does not exist.
58
+ *
59
+ * ## The ALPHABET and the GRAMMAR are two different tables
60
+ *
61
+ * The lesson `c4/rules.ts` records at length, and UML needs it more, having nine
62
+ * edge roles where C4 has one. `relation-endpoints` raises an off-matrix finding
63
+ * for every rule declaring a matrix, so two rules sharing one grammar report the
64
+ * same wrong sentence TWICE. Here the split is per EDGE ROLE: each grammar table
65
+ * below is held by exactly one rule, and the one place two rules read the same
66
+ * edge role — {@link untypedEdge} and {@link actorActorAssociation}, both on
67
+ * `uml:association` — is the C4 arrangement exactly, the neutral rule carrying a
68
+ * matrix so permissive that off-matrix is structurally unreachable for it.
69
+ *
70
+ * ## The ALPHABET is also how a rule gets something to SAY
71
+ *
72
+ * The other half of the same mechanism, and the one that shapes three tables
73
+ * here. An end whose role is outside a rule's alphabet is not evaluated at all
74
+ * — the family's hard proportionality requirement — so a rule whose only
75
+ * sanctioned sentence is `use case → use case` has an alphabet of one role and
76
+ * can NEVER fire: an include drawn from an actor is silently outside it. Data
77
+ * that can never fire and never says why is the worst thing declarative data can
78
+ * do.
79
+ *
80
+ * So {@link UML_USE_CASE_MATRIX} and {@link UML_GENERALIZATION_MATRIX} each
81
+ * carry one or two triplets whose EDGE role is not the rule's own. They are true
82
+ * sentences of the notation — an actor associates with a use case (§18.1.3), two
83
+ * instances are joined by a link (§9.8.4) — and `inMatrix` tests the edge role
84
+ * with `roleIsA`, so such a triplet can never sanction the edge the rule is
85
+ * about. Their whole effect is to put `uml:actor` and `uml:object` into the
86
+ * alphabet, which is what makes "an include with an actor at one end" and "a
87
+ * generalization pointing at an instance" findings rather than silence.
88
+ *
89
+ * ## What the whole file stays silent about
90
+ *
91
+ * A diagram drawn before the roles existed carries no role on anything, so it is
92
+ * never evaluated and never says a word (PRD principle 8). A sheet drawn with no
93
+ * `uml:diagram` frame round it has no frame for the membership rules to be
94
+ * about, and they are silent by construction — a croquis on bare canvas is a
95
+ * croquis. A use case diagram with no SUBJECT drawn on it is the same silence one
96
+ * frame in: {@link useCaseOutsideSubject} and {@link actorInsideSubject} both
97
+ * need a subject to be inside or outside OF.
98
+ *
99
+ * ## Six questions this pack deliberately does NOT ask
100
+ *
101
+ * Not gaps in the notation — gaps in what the engine's families can be asked of
102
+ * a UML element. The first two are phase 1's; the next is the structural
103
+ * sheets', one of the two rules that brief asked for and this file could not
104
+ * honestly write. The last three are the behaviour sheets', and two of them are
105
+ * the same shape: a rule the brief asked for that ALREADY EXISTS under another
106
+ * name, and would have reported one mistake twice.
107
+ *
108
+ * It was SEVEN until the recette of 2026-09-16, and the one that left is worth
109
+ * a line here because of how it left. `uml.port-on-border` was recorded in this
110
+ * list as unwritable — no family expressed "within a band of that box's
111
+ * outline" — and the PO dragged a port into the middle of a component and
112
+ * asked why nothing happened. The answer was not to approximate it with a
113
+ * family that asks a neighbouring question; it was to write the family the
114
+ * question needs (`border-proximity`, ADR 0024), which is the second time this
115
+ * pack has done that and the second time it was the right call (ADR 0021 is the
116
+ * first). A recorded gap is a gap somebody can close. The six below are the ones
117
+ * still open, and each says what would close it.
118
+ *
119
+ * - **an artefact of any kind drawn outside the frame.** `element-in-background`
120
+ * names ONE subject role, and UML's vocabulary has no single role meaning "any
121
+ * UML artefact": `uml:classifier` is the spec's own generalisation of a class,
122
+ * an interface and an enumeration (§9.2) and reaches nothing else, because an
123
+ * object is an instance and a package is a namespace — filing them under it
124
+ * would make every rule about classifiers fall on them. So
125
+ * {@link elementOutsideFrame} is written on the classifiers, which is what a
126
+ * class diagram is made of, and an object, a package, a note, an actor or a use
127
+ * case parked beside the sheet is silence. The same shape of limit C4 records
128
+ * for its empty-boundary rule, and it closes the day a family accepts several
129
+ * subject roles.
130
+ * - **the MULTIPLICITY on an association end.** UML writes it at the ends, this
131
+ * canvas has one label per connector, and `docs/adr/0018` is the record of why
132
+ * phase 1 puts the words in the centre label. Until a connector carries per-end
133
+ * text there is nothing for a rule to read, and asking for a multiplicity in
134
+ * the middle of the line would be asking the author to write the notation
135
+ * wrongly so the tool could check it.
136
+ * - **a lollipop or socket attached to nothing** (`uml.interface-near-component`,
137
+ * §10.4.4, §11.6.4). The port's neighbour, and `border-proximity` does NOT
138
+ * close it: that family judges a glyph the author has already put ON a box —
139
+ * overlap is its gate — and a lollipop is drawn BESIDE one, touching it at a
140
+ * gap rather than straddling its edge. Asking "is this stub within 24 units of
141
+ * some component" is a third geometry again (`attachment` measures adjacency to
142
+ * a path, `no-overlap` forbids a collision rather than requiring a
143
+ * proximity), and it is the one `model.ts` already reads for the exporters. So
144
+ * the adjacency stays there and no rule is written on it. What would close it
145
+ * is `border-proximity` gaining an outward reading — "near the outline, inside
146
+ * or out" — and that is a different requirement rather than a wider tolerance:
147
+ * a stub floating in open canvas would then need a rule about it, which is a
148
+ * remark about a sketch.
149
+ * - **an ACTION with no name** (`uml.unnamed-action`) and **a STATE with no
150
+ * name** (`uml.unnamed-state`). Both are already reported, and adding them
151
+ * would report them twice. `label-presence` reads an element's OWN words and
152
+ * names one subject role, so the subject of a naming rule is the TIER and
153
+ * never the shape it is grouped with — which is why
154
+ * {@link unnamedActorOrUseCase} is one rule for two artefacts and says so at
155
+ * length. An action, an object node and the three signal artefacts carry
156
+ * `uml:label`, so an emptied one is already that rule's finding; a state
157
+ * carries `uml:name`, so an emptied one is already {@link unnamedClassifier}'s.
158
+ * A second rule on either tier would put two brackets and two suggestions on
159
+ * one word to fix, one of which would always be about the wrong shape —
160
+ * exactly the failure the existing pair was collapsed to avoid.
161
+ *
162
+ * What IS wrong is the wording: "This actor or use case has no name" is the
163
+ * sentence an author of an emptied action reads. The fix is a rename of the
164
+ * two existing rules to the tier they are actually about
165
+ * (`uml.unnamed-label`, `uml.unnamed-name`) with the words widened to match,
166
+ * which touches their ids, their message keys, both profiles and the host
167
+ * catalogues — a change worth making on its own rather than smuggled in
168
+ * beside fifteen new rules. Recorded here so it stays a decision.
169
+ *
170
+ * **2026-09-16: the rename is BLOCKED, and the list above is why it looked
171
+ * cheap.** It misses the one reader that is not code: a DOCUMENT. An id is
172
+ * not merely declared and displayed, it is STORED — "excuse this finding"
173
+ * writes it into the element's own
174
+ * `GfxPrimitiveElementModel.validationExceptions`, a `@field()`, so it lands
175
+ * in the Y.Map, syncs to every peer and survives a copy. Rename the rule and
176
+ * no stored exception matches it any more: every waiver a user granted
177
+ * against these two lapses in silence, the finding returns on a board that
178
+ * had settled it, and the orphaned entry stays behind for good. Only
179
+ * `validationProfile` was thought to reach a document; exceptions are the
180
+ * second path and they are keyed by RULE id. So the wording can still be
181
+ * widened — `messageFallback` and the message keys are display and nothing
182
+ * stores them — but the IDS are frozen until something migrates
183
+ * `validationExceptions`, which is a store concern. See `docs/adr/0017`.
184
+ * - **one beginning per REGION** rather than per sheet. §14.5.6.4 bounds the
185
+ * initial vertex per Region and a state machine with three composite states
186
+ * legitimately draws four discs; `role-count` counts per instance of ONE
187
+ * frame, so {@link initialSingle} counts per `uml:diagram` and the per-region
188
+ * count is a second rule of the same family against `uml:region`. It is a
189
+ * phase-3 refinement rather than a limit of the engine — the family already
190
+ * expresses it — and until it lands the rule is `audit` in both profiles for
191
+ * that reason as much as for any other.
192
+ *
193
+ * ## Two things a HOST reading these findings has to know
194
+ *
195
+ * Both came out of the tranche-F recette, both are engine behaviour rather than
196
+ * anything this file declares, and both make a check-up look wrong to somebody
197
+ * who does not know them.
198
+ *
199
+ * - **a membership finding is attributed to the LANE, not to the sheet.**
200
+ * `uml.node-in-partition` and the `*-history-outside-region` pair are
201
+ * `element-in-background` rules whose SUBJECT is the band or the composite
202
+ * state — that is what "inside" is measured against — so the finding is filed
203
+ * under the `uml:partition` or the `uml:region` it is about. A check-up run on
204
+ * the FRAME alone therefore shows none of them, and a host that offers "check
205
+ * this diagram" by selecting the `umlDiagram` has to include the backgrounds
206
+ * drawn on it, or it will report a clean sheet over an action drawn between
207
+ * two lanes.
208
+ * - **a strict profile MOVES a finding, it does not add one.** Promoting a rule
209
+ * past `audit` takes it out of the check-up's own list and into the live
210
+ * `violations$` path, by the engine's design. So switching `uml.sketch` for
211
+ * `uml.strict` makes a check-up report FEWER rows, not more, and the rows that
212
+ * left are the ones now drawn on the canvas. A host presenting the two counts
213
+ * side by side has to say which is which.
214
+ */
215
+ /* ── The alphabets and the grammars ─────────────────────────────────────── */
216
+ /**
217
+ * Every UML role that is an ELEMENT of the model — the alphabet a role-less
218
+ * connector is read against, and nothing more.
219
+ *
220
+ * `uml:classifier` stands for its three children by declaration, so the class,
221
+ * the interface and the enumeration arrive through `roleIsA` rather than as
222
+ * three entries. The two FRAMES are absent and meant to be: a connector dragged
223
+ * onto the sheet or onto a subject is somebody pointing at something, and
224
+ * pointing at things is what a whiteboard is for. The three TIER roles are
225
+ * absent for the same reason — a line drawn onto an attributes compartment is an
226
+ * annotation.
227
+ *
228
+ * ## Phase 2 appended six, and the two frames are still absent
229
+ *
230
+ * The component, its port, the two interface glyphs, the artefact and the node
231
+ * (parent of the device and the execution environment) joined the day the
232
+ * structural sheets did. They had to: a wiring gesture between two ports and a
233
+ * quick-connect between two cubes are exactly the drawings §11.6.4 and §19.4.4
234
+ * expect to be TYPED, and a role-less line between them says nothing to the
235
+ * grammar, nothing to the degree counts and nothing to either exporter — which
236
+ * is the whole subject of {@link untypedEdge}.
237
+ */
238
+ const UML_ELEMENT_ROLES = [
239
+ UML_ROLE.classifier,
240
+ UML_ROLE.object,
241
+ UML_ROLE.package,
242
+ UML_ROLE.note,
243
+ UML_ROLE.actor,
244
+ UML_ROLE['use-case'],
245
+ // Phase 2 — the structural vocabulary. `uml:node` stands for its two children.
246
+ UML_ROLE.component,
247
+ UML_ROLE.port,
248
+ UML_ROLE['provided-interface'],
249
+ UML_ROLE['required-interface'],
250
+ UML_ROLE.artifact,
251
+ UML_ROLE.node,
252
+ // Phase 2 — the BEHAVIOUR vocabulary, and the same argument a third time: a
253
+ // quick-connect between two actions, or between two states, is the drawing
254
+ // §15.2.4 and §14.2.4 expect to be a control flow and a transition. A line
255
+ // carrying no role between them says nothing to either grammar, nothing to the
256
+ // degree counts, nothing to the two reachability walks and nothing to either
257
+ // exporter. `uml:control-node` stands for its five children and
258
+ // `uml:pseudostate` for its seven.
259
+ UML_ROLE.action,
260
+ UML_ROLE['control-node'],
261
+ UML_ROLE['object-node'],
262
+ UML_ROLE['send-signal'],
263
+ UML_ROLE['accept-event'],
264
+ UML_ROLE['time-event'],
265
+ UML_ROLE.state,
266
+ UML_ROLE['final-state'],
267
+ UML_ROLE.pseudostate,
268
+ ];
269
+ /**
270
+ * Every ordered pair of the element roles — the ALPHABET, and not a grammar.
271
+ *
272
+ * This table sanctions all four hundred and forty-one sentences — twenty-one
273
+ * element roles squared, since the behaviour vocabulary joined the alphabet —
274
+ * so a rule holding it judges none of them. It exists so {@link untypedEdge}
275
+ * can say "between two
276
+ * UML artefacts" — which is the only thing `flagNeutral` reads a matrix FOR —
277
+ * without inheriting a judgement that belongs to {@link actorActorAssociation}.
278
+ */
279
+ export const UML_ELEMENT_MATRIX = UML_ELEMENT_ROLES.flatMap(source => UML_ELEMENT_ROLES.map(target => ({
280
+ source,
281
+ edge: UML_ROLE.association,
282
+ target,
283
+ })));
284
+ /**
285
+ * What an ASSOCIATION may run between: anything the notation associates, except
286
+ * two actors.
287
+ *
288
+ * §18.1.3 is explicit — an Actor may have Associations with UseCases, Components
289
+ * and Classes, and with nothing else — so two stick figures joined by a solid
290
+ * line is the one removal, exactly as person → person is C4's. Everything else
291
+ * in the alphabet is sanctioned deliberately: a classifier associates with a
292
+ * classifier (§11.5.4), two instances are joined by a LINK drawn the same way
293
+ * (§9.8.4), and an actor associates with a use case.
294
+ *
295
+ * The package and the note are outside the alphabet, so an association drawn
296
+ * onto one is not evaluated. That is the family's proportionality requirement
297
+ * doing its job: a line to a note is an anchor somebody drew with the wrong
298
+ * tool, which {@link untypedEdge} is the rule for.
299
+ *
300
+ * Exported so a test asserts THIS table rather than a copy of it.
301
+ */
302
+ const UML_ASSOCIATION_ROLES = [
303
+ UML_ROLE.classifier,
304
+ UML_ROLE.object,
305
+ UML_ROLE.actor,
306
+ UML_ROLE['use-case'],
307
+ ];
308
+ export const UML_ASSOCIATION_MATRIX = UML_ASSOCIATION_ROLES.flatMap(source => UML_ASSOCIATION_ROLES.map(target => ({
309
+ source,
310
+ edge: UML_ROLE.association,
311
+ target,
312
+ }))).filter(triplet => !(triplet.source === UML_ROLE.actor && triplet.target === UML_ROLE.actor));
313
+ /**
314
+ * The roles a GENERALIZATION may be drawn between — the alphabet of both
315
+ * generalization rules, and the reason the instance is in it.
316
+ *
317
+ * §9.9.7: "the general classifier must be of the same kind as the specific
318
+ * classifier", which gives the four diagonal sentences of
319
+ * {@link UML_GENERALIZATION_MATRIX}. `uml:object` is here and is sanctioned by
320
+ * nothing: an instance specialises nothing and is specialised by nothing (§9.8),
321
+ * and a hollow triangle pointing at one is a generalization drawn onto the
322
+ * picture of a runtime value.
323
+ */
324
+ const UML_GENERALIZATION_ROLES = [
325
+ UML_ROLE.class,
326
+ UML_ROLE.interface,
327
+ UML_ROLE.actor,
328
+ UML_ROLE['use-case'],
329
+ UML_ROLE.object,
330
+ ];
331
+ /**
332
+ * The permissive table — every ordered pair of {@link UML_GENERALIZATION_ROLES}.
333
+ *
334
+ * Held by {@link generalizationSelfLoop} alone, for the reason C4 states: a
335
+ * second rule carrying the real grammar would report every wrong sentence a
336
+ * second time, with two brackets and two suggestions for one gesture to fix. The
337
+ * loop rule needs the same ALPHABET (so a generalization looped onto a note or a
338
+ * frame stays outside the conversation) and no grammar at all.
339
+ */
340
+ export const UML_GENERALIZATION_ALPHABET = UML_GENERALIZATION_ROLES.flatMap(source => UML_GENERALIZATION_ROLES.map(target => ({
341
+ source,
342
+ edge: UML_ROLE.generalization,
343
+ target,
344
+ })));
345
+ /**
346
+ * The generalization GRAMMAR: like specialises like, and an instance is not a
347
+ * kind of anything.
348
+ *
349
+ * Four diagonal sentences and one ALPHABET entry. The fifth triplet is a link
350
+ * between two instances (§9.8.4) — a true sentence, carrying the ASSOCIATION
351
+ * role — and it is here so that `uml:object` is a role this rule speaks about:
352
+ * without it a generalization drawn onto an instance would be outside the
353
+ * alphabet and silently unjudged. `inMatrix` matches the edge role with
354
+ * `roleIsA`, so it can never sanction a generalization.
355
+ *
356
+ * The enumeration is absent from both ends, and that is a silence rather than a
357
+ * permission: a generalization between two enumerations is legal in the
358
+ * metamodel (an Enumeration is a DataType) and vanishingly rare on a drawing, so
359
+ * the pack declines to have an opinion about it.
360
+ */
361
+ export const UML_GENERALIZATION_MATRIX = [
362
+ {
363
+ source: UML_ROLE.class,
364
+ edge: UML_ROLE.generalization,
365
+ target: UML_ROLE.class,
366
+ },
367
+ {
368
+ source: UML_ROLE.interface,
369
+ edge: UML_ROLE.generalization,
370
+ target: UML_ROLE.interface,
371
+ },
372
+ {
373
+ source: UML_ROLE.actor,
374
+ edge: UML_ROLE.generalization,
375
+ target: UML_ROLE.actor,
376
+ },
377
+ {
378
+ source: UML_ROLE['use-case'],
379
+ edge: UML_ROLE.generalization,
380
+ target: UML_ROLE['use-case'],
381
+ },
382
+ // The ALPHABET entry — see the header. A link between two instances, on the
383
+ // association role, so it sanctions no generalization whatever.
384
+ {
385
+ source: UML_ROLE.object,
386
+ edge: UML_ROLE.association,
387
+ target: UML_ROLE.object,
388
+ },
389
+ ];
390
+ /**
391
+ * What a REALIZATION points at: an interface, and nothing else (§10.4.3, whose
392
+ * `contract` end is typed `Interface`).
393
+ *
394
+ * One sentence, and an alphabet of one role — `uml:interface` specialises
395
+ * `uml:classifier`, so the source entry covers both ends of every realization
396
+ * drawn between two compartmented boxes. A realization onto a package, an
397
+ * object, an actor or a use case is outside the alphabet and is not judged:
398
+ * those are drawings of something else entirely, and the notation has no reading
399
+ * of them to correct.
400
+ */
401
+ export const UML_REALIZATION_MATRIX = [
402
+ {
403
+ source: UML_ROLE.classifier,
404
+ edge: UML_ROLE.realization,
405
+ target: UML_ROLE.interface,
406
+ },
407
+ ];
408
+ /**
409
+ * What a DEPENDENCY may run between: the model's named elements, and never TO an
410
+ * instance.
411
+ *
412
+ * A dependency is a statement about the model — this classifier needs that one,
413
+ * this package needs that one (§7.8.4) — and an INSTANCE is not part of the
414
+ * model but an illustration of it (§9.8.3). An arrow pointing at `o1 : Order` is
415
+ * almost always an arrow meant for `Order`.
416
+ *
417
+ * The object is therefore a legal SOURCE and never a legal TARGET, which is the
418
+ * whole content of the table: three sources, two targets, six sentences, and
419
+ * `uml:object` in the alphabet so the seventh is a finding.
420
+ */
421
+ const UML_DEPENDENCY_SOURCES = [
422
+ UML_ROLE.classifier,
423
+ UML_ROLE.package,
424
+ UML_ROLE.object,
425
+ ];
426
+ const UML_DEPENDENCY_TARGETS = [
427
+ UML_ROLE.classifier,
428
+ UML_ROLE.package,
429
+ ];
430
+ export const UML_DEPENDENCY_MATRIX = UML_DEPENDENCY_SOURCES.flatMap(source => UML_DEPENDENCY_TARGETS.map(target => ({
431
+ source,
432
+ edge: UML_ROLE.dependency,
433
+ target,
434
+ })));
435
+ /**
436
+ * The use case diagram's grammar, whole — read by TWO rules, each seeing the
437
+ * slice its own edge role selects.
438
+ *
439
+ * §18.1.3 types both ends of an Include (`addition: UseCase`) and both ends of
440
+ * an Extend (`extendedCase: UseCase`), so the only sentence either sanctions is
441
+ * use case → use case. That alone would give each rule an alphabet of one role
442
+ * and nothing it could ever report — an include drawn from an ACTOR, which is
443
+ * the mistake these two exist for, would be outside the alphabet and silent.
444
+ *
445
+ * The two ASSOCIATION triplets are what fix that. They are true sentences of
446
+ * §18.1.3 (an actor associates with a use case) and they carry the association
447
+ * role, so `inMatrix` — which matches a triplet's edge with `roleIsA` — can never
448
+ * let them sanction an include or an extend. Their only effect is to put
449
+ * `uml:actor` in the alphabet both rules speak.
450
+ *
451
+ * The two rules never double-report: an edge carries one role, and the include
452
+ * rule reads `uml:include` while the extend rule reads `uml:extend` — flat
453
+ * siblings, so neither reaches the other's edges, and neither reaches the
454
+ * associations this table also describes.
455
+ */
456
+ export const UML_USE_CASE_MATRIX = [
457
+ {
458
+ source: UML_ROLE['use-case'],
459
+ edge: UML_ROLE.include,
460
+ target: UML_ROLE['use-case'],
461
+ },
462
+ {
463
+ source: UML_ROLE['use-case'],
464
+ edge: UML_ROLE.extend,
465
+ target: UML_ROLE['use-case'],
466
+ },
467
+ // The ALPHABET entries — see the header.
468
+ {
469
+ source: UML_ROLE.actor,
470
+ edge: UML_ROLE.association,
471
+ target: UML_ROLE['use-case'],
472
+ },
473
+ {
474
+ source: UML_ROLE['use-case'],
475
+ edge: UML_ROLE.association,
476
+ target: UML_ROLE.actor,
477
+ },
478
+ ];
479
+ /**
480
+ * What a DEPLOY may run between: an artefact onto a node, and nothing else.
481
+ *
482
+ * §19.4.4 draws it as the dashed arrow with the `«deploy»` keyword, and §19.2.3
483
+ * types its two ends — a DeployedArtifact onto a DeploymentTarget. `uml:node` is
484
+ * the parent of `uml:device` and `uml:execution-environment` (`roles.ts`), so the
485
+ * single sentence below reaches all three targets through `roleIsA` rather than
486
+ * being restated once per cube.
487
+ *
488
+ * The second triplet is an ALPHABET entry, the device the file header explains:
489
+ * a manifestation is a true sentence of §19.3.4, it carries the `uml:manifest`
490
+ * role and can therefore never sanction a deploy, and its whole effect is to put
491
+ * `uml:component` into the alphabet this rule speaks. Without it a `«deploy»`
492
+ * drawn from a component — which is the mistake this rule exists for, since a
493
+ * component is not what gets deployed, its artefact is — would be outside the
494
+ * alphabet and silently unjudged.
495
+ */
496
+ export const UML_DEPLOY_MATRIX = [
497
+ {
498
+ source: UML_ROLE.artifact,
499
+ edge: UML_ROLE.deploy,
500
+ // The PARENT role: a node, a device and an execution environment alike.
501
+ target: UML_ROLE.node,
502
+ },
503
+ // The ALPHABET entry — see the header.
504
+ {
505
+ source: UML_ROLE.artifact,
506
+ edge: UML_ROLE.manifest,
507
+ target: UML_ROLE.component,
508
+ },
509
+ ];
510
+ /**
511
+ * What a MANIFEST may run between: an artefact onto the element it embodies.
512
+ *
513
+ * §19.3.3 — "An Artifact may embody, or manifest, a number of model elements" —
514
+ * and §19.3.4 draws it as the dashed open arrow labelled `«manifest»`. The
515
+ * metamodel's `utilizedElement` is a PackageableElement, so a manifestation may
516
+ * point at a great many things; this pack draws the one §19.3.5's own figure
517
+ * draws, "A Manifestation relationship between an Artifact and a Component",
518
+ * because a component diagram is where a manifestation is drawn at all.
519
+ *
520
+ * {@link UML_DEPLOY_MATRIX}'s twin, mirrored: the foreign triplet here is the
521
+ * DEPLOY, which puts `uml:node` in the alphabet so that a `«manifest»` aimed at
522
+ * a cube — the same arrow drawn at the wrong end of the deployment story — is a
523
+ * finding rather than silence.
524
+ */
525
+ export const UML_MANIFEST_MATRIX = [
526
+ {
527
+ source: UML_ROLE.artifact,
528
+ edge: UML_ROLE.manifest,
529
+ target: UML_ROLE.component,
530
+ },
531
+ // The ALPHABET entry — see the header.
532
+ {
533
+ source: UML_ROLE.artifact,
534
+ edge: UML_ROLE.deploy,
535
+ target: UML_ROLE.node,
536
+ },
537
+ ];
538
+ /**
539
+ * What a COMMUNICATION PATH may run between: two nodes.
540
+ *
541
+ * §19.4.3 is as narrow as a clause gets — "A CommunicationPath is an Association
542
+ * between two DeploymentTargets, through which they may exchange Signals and
543
+ * Messages" — and §19.4.4 draws it as the plain association link between two
544
+ * cubes. One sentence, and `uml:node` on both ends, so a device talking to an
545
+ * execution environment is sanctioned through the parent role.
546
+ *
547
+ * TWO alphabet entries this time, and both are foreign edges: they put
548
+ * `uml:artifact` and `uml:component` into the alphabet, which is what makes a
549
+ * network line drawn onto a `.jar` or onto a component box a finding. A
550
+ * communication path is about MACHINES talking to machines, and the wire drawn
551
+ * to the software running on one of them is the confusion the rule is for.
552
+ */
553
+ export const UML_COMMUNICATION_PATH_MATRIX = [
554
+ {
555
+ source: UML_ROLE.node,
556
+ edge: UML_ROLE['communication-path'],
557
+ target: UML_ROLE.node,
558
+ },
559
+ // The ALPHABET entries — see the header.
560
+ {
561
+ source: UML_ROLE.artifact,
562
+ edge: UML_ROLE.deploy,
563
+ target: UML_ROLE.node,
564
+ },
565
+ {
566
+ source: UML_ROLE.artifact,
567
+ edge: UML_ROLE.manifest,
568
+ target: UML_ROLE.component,
569
+ },
570
+ ];
571
+ /* ── Membership: is the drawing on the sheet it claims to be on? ────────── */
572
+ /**
573
+ * **U1** — a classifier drawn beside the diagram frame.
574
+ *
575
+ * Annex A draws a UML diagram inside a frame carrying its kind and its name, and
576
+ * what is outside the frame is outside the diagram: a class parked on bare
577
+ * canvas belongs to no sheet, is written into no export (`export.ts` attributes
578
+ * by frame) and says nothing a reader can situate.
579
+ *
580
+ * Requiring it to be drawn INSIDE the rectangle is ours rather than the
581
+ * specification's — Annex A describes the frame, it does not forbid ink beside
582
+ * one — so the provenance says `labre-convention`, the same call C4's
583
+ * `homeless-component` makes.
584
+ *
585
+ * ## Written on the CLASSIFIERS, and the limit that follows
586
+ *
587
+ * `element-in-background` names one subject role and UML declares no ancestor
588
+ * meaning "any artefact" (the file header says why at length). So this rule
589
+ * reaches a class, an interface and an enumeration — what a class diagram is made
590
+ * of — and an object, a package, a note, an actor or a use case parked outside
591
+ * the frame raises nothing. A narrower rule that is right is worth more than a
592
+ * wider one that guesses.
593
+ *
594
+ * Phase 2 widened `appliesTo` by nothing, and that is the same limit rather than
595
+ * an oversight: a component, a port, an interface glyph, an artefact and a node
596
+ * have no common ancestor with the three classifiers that does not ALSO reach
597
+ * the frames and the tiers, and inventing one — `uml:artefact`, say — would be
598
+ * this pack adding a word to a vocabulary whose whole authority is that every
599
+ * role in it is the specification's. Six artefacts parked beside the sheet
600
+ * raised nothing before; twelve do now.
601
+ *
602
+ * ## Silence on a frameless sketch
603
+ *
604
+ * A board with no `uml:diagram` on it has no frame for this question to be about,
605
+ * and the family answers that with silence — which covers every diagram drawn
606
+ * before the frame existed, and every croquis somebody is still deciding the
607
+ * shape of.
608
+ */
609
+ const elementOutsideFrame = {
610
+ id: 'uml.element-outside-frame',
611
+ framework: 'uml',
612
+ family: 'element-in-background',
613
+ severity: 'audit',
614
+ appliesTo: UML_ROLE.classifier,
615
+ roles: UML_ROLES,
616
+ messageKey: 'com.labre.uml.validation.element-outside-frame',
617
+ messageFallback: 'This classifier sits outside the diagram frame.',
618
+ suggestionKey: 'com.labre.uml.validation.element-outside-frame.suggestion',
619
+ suggestionFallback: 'A UML diagram is what the frame encloses: its heading says which kind of diagram this is, and the export reads the artefacts inside it. Move the box onto the sheet, or stretch the frame round it.',
620
+ version: 1,
621
+ provenance: {
622
+ source: 'labre-convention',
623
+ reference: 'Labre convention — Annex A draws the frame; requiring an artefact to be inside one is membership on this canvas',
624
+ },
625
+ // The frame, and the whole question. No `background` declaration:
626
+ // `element-in-background` measures against the element BOX and reads no
627
+ // margin, so a geometry declaration here would be data nothing reads.
628
+ backgroundRole: UML_ROLE.diagram,
629
+ };
630
+ /**
631
+ * The five SEQUENCE artefacts, as one entry for the eight deny-lists that all
632
+ * refuse the same five (§17.2.4, §17.3.4, §17.4.4, §17.6.4).
633
+ *
634
+ * Spread into each list rather than named eight times, and that is a departure
635
+ * from how the `act` and `stm` lists are written — they spell their strangers
636
+ * out one by one, on purpose, because each of them refuses a DIFFERENT subset of
637
+ * the other's routing glyphs and a shared constant would have hidden which. Here
638
+ * the subset is identical in all eight, and writing it once is what makes it
639
+ * impossible for a ninth sheet to be added with four of the five.
640
+ *
641
+ * ## `uml:message` is the first EDGE role this table names, and it belongs
642
+ *
643
+ * Every other entry in `forbidden` is a node or a frame, which is a gap in the
644
+ * eight lists rather than a reading of the family: `evaluateViewAdmissibility`
645
+ * judges any element carrying a role, and a connector carries one and has
646
+ * bounds. A message arrow is also the one edge in this vocabulary whose meaning
647
+ * is entirely about the sheet it is on — §17.4.4 reads it against a vertical
648
+ * axis of TIME — so a message drawn across a class diagram is not an edge of the
649
+ * wrong type between two boxes, it is an arrow whose whole notation is missing.
650
+ * The parent role reaches all five sorts in one entry.
651
+ */
652
+ const UML_SEQUENCE_ROLES = [
653
+ UML_ROLE.lifeline,
654
+ UML_ROLE.execution,
655
+ UML_ROLE.destruction,
656
+ UML_ROLE.fragment,
657
+ UML_ROLE.message,
658
+ ];
659
+ /**
660
+ * **U2** — an artefact drawn on a sheet whose own heading says it is a different
661
+ * diagram.
662
+ *
663
+ * The rule whose subject is the SHEET, and the one the diagram-kind picker
664
+ * exists for. A UML frame does not merely have a name: Annex A writes its
665
+ * heading as `<kind> <name>` — `class Orders`, `uc Checkout` — so every frame
666
+ * states which of the six diagrams it draws, and stating it is not optional the
667
+ * way a C4 board's level is (`kinds.ts` says why). The rule reads that statement
668
+ * back and confronts it with what has been drawn.
669
+ *
670
+ * Nine kinds since phase 3, and the table below grows by APPENDING a key: a
671
+ * `forbidden` entry is looked up by the frame's own `kind` string, so a kind with
672
+ * no entry is a sheet this rule has nothing to say about — which is exactly what
673
+ * a phase-1 build does with a `cmp` frame, and a phase-2 build with an `sd` one.
674
+ *
675
+ * ## The deny-lists, and the much longer list they do not name
676
+ *
677
+ * A **class** diagram refuses the use case vocabulary: actors, use cases and the
678
+ * subject rectangle are §18 and belong on a `uc` sheet. It admits classes,
679
+ * interfaces, enumerations, objects, packages and notes — a class diagram
680
+ * legitimately shows an instance beside the classifier it illustrates.
681
+ *
682
+ * A **package** diagram refuses that same vocabulary and the OBJECT besides:
683
+ * §12.2.4 draws packages, the classifiers they contain and the dependencies
684
+ * between them, and an instance on such a sheet is a level the diagram has not
685
+ * zoomed to.
686
+ *
687
+ * An **object** diagram refuses the use case vocabulary and the CLASSIFIERS:
688
+ * §9.8.4 draws instances and the links between them, which is the whole point of
689
+ * the sheet — a class drawn among them is the type, not one of its values.
690
+ *
691
+ * A **use case** diagram refuses the class-diagram vocabulary: classifiers,
692
+ * objects and packages. It admits actors, use cases, the subject and notes.
693
+ *
694
+ * Nothing else is named anywhere: notes, legend glyphs, sticky notes, free
695
+ * rectangles somebody thought with and every artefact of another framework are
696
+ * left alone, because a deny-list says only what the notation refuses (PRD
697
+ * principle 8).
698
+ *
699
+ * ## What it stays silent about
700
+ *
701
+ * An artefact drawn OUTSIDE the frame — the family judges a subject against the
702
+ * frame whose plot contains its centre, so a box beside the sheet is
703
+ * {@link elementOutsideFrame}'s business and not this rule's. And a frame
704
+ * carrying a kind this build has never heard of (a phase-2 value on a phase-1
705
+ * build, an import) is a level with no entry in the table, which the engine
706
+ * answers by walking nothing at all.
707
+ */
708
+ const notAdmissibleOnKind = {
709
+ id: 'uml.not-admissible-on-kind',
710
+ framework: 'uml',
711
+ family: 'view-admissibility',
712
+ severity: 'audit',
713
+ // No `appliesTo`: the subjects are declared per KIND, in `admissibility`, and
714
+ // naming a single one here would be data that lies.
715
+ roles: UML_ROLES,
716
+ messageKey: 'com.labre.uml.validation.not-admissible-on-kind',
717
+ messageFallback: 'This artefact is not drawn on the kind of diagram the frame says this is.',
718
+ suggestionKey: 'com.labre.uml.validation.not-admissible-on-kind.suggestion',
719
+ suggestionFallback: 'The frame’s heading names the diagram — class, pkg, obj, uc, cmp, dep, act, stm or sd — and each draws its own vocabulary. Move the artefact to a sheet that draws it, or change the frame’s kind to the one it really shows.',
720
+ version: 1,
721
+ provenance: {
722
+ source: 'recommendation',
723
+ reference: 'OMG UML 2.5.1 — Annex A (diagram kinds and frame headings), §9.8.4 (object diagrams), §12.2.4 (package diagrams), §18.1.4 (use case diagrams), §17.2.4 (sequence diagrams)',
724
+ },
725
+ backgroundRole: UML_ROLE.diagram,
726
+ admissibility: {
727
+ // The prop the frame writes its kind in. The rule names it; the engine reads
728
+ // it; nothing in between knows the word "UML".
729
+ levelProp: 'kind',
730
+ forbidden: {
731
+ class: [
732
+ UML_ROLE.actor,
733
+ UML_ROLE['use-case'],
734
+ UML_ROLE.subject,
735
+ // Phase 3 — the SEQUENCE vocabulary, refused by every sheet that is not
736
+ // `sd`. A lifeline, an execution bar, a destruction cross, a combined
737
+ // fragment and a message arrow are §17's own notation, and the spine of
738
+ // a lifeline draws TIME running down the page — the one frame of
739
+ // reference the other eight sheets explicitly do not have
740
+ // (`background.ts`: "a UML diagram is a GRAPH"). So they are refused
741
+ // everywhere else, and the seven lists below name the same five.
742
+ ...UML_SEQUENCE_ROLES,
743
+ ],
744
+ pkg: [
745
+ UML_ROLE.actor,
746
+ UML_ROLE['use-case'],
747
+ UML_ROLE.subject,
748
+ UML_ROLE.object,
749
+ ...UML_SEQUENCE_ROLES,
750
+ ],
751
+ obj: [
752
+ UML_ROLE.actor,
753
+ UML_ROLE['use-case'],
754
+ UML_ROLE.subject,
755
+ // The PARENT role: a class, an interface and an enumeration alike.
756
+ UML_ROLE.classifier,
757
+ ...UML_SEQUENCE_ROLES,
758
+ ],
759
+ uc: [
760
+ UML_ROLE.classifier,
761
+ UML_ROLE.object,
762
+ UML_ROLE.package,
763
+ ...UML_SEQUENCE_ROLES,
764
+ ],
765
+ // Phase 2 — the two structural sheets.
766
+ //
767
+ // A **component** diagram (§11.6.4) refuses the use case vocabulary and
768
+ // the instance, for the reasons the class and package lists refuse them,
769
+ // and it refuses the DEPLOYMENT cubes besides: a node, a device and an
770
+ // execution environment are §19.4's answer to "where does this run", which
771
+ // is the question the `dep` sheet is drawn to ask. It admits the component
772
+ // itself, its ports, the two interface glyphs, an interface drawn as a
773
+ // full rectangle instead (§11.6.4 offers both), the packages that group
774
+ // them, the notes, and the ARTEFACTS that manifest a component — §11.6.5's
775
+ // own "white box" figure lists them in a compartment.
776
+ cmp: [
777
+ UML_ROLE.actor,
778
+ UML_ROLE['use-case'],
779
+ UML_ROLE.subject,
780
+ UML_ROLE.object,
781
+ // The PARENT role: a node, a device and an execution environment alike.
782
+ UML_ROLE.node,
783
+ ...UML_SEQUENCE_ROLES,
784
+ ],
785
+ // A **deployment** diagram (§19.2.4) refuses the use case vocabulary, the
786
+ // instance and the three CLASSIFIERS: §19 draws machines, the software
787
+ // environments on them, the artefacts deployed onto those and the
788
+ // components those artefacts manifest — a class, an interface or an
789
+ // enumeration on such a sheet is a level the diagram has not zoomed to.
790
+ //
791
+ // The three are named one by one where the `obj` list above names their
792
+ // parent, and that is deliberate rather than untidy. `uml:classifier` is
793
+ // the spec's own generalisation (§9.2) and the metamodel ALSO makes a
794
+ // Component (§11.6.2) and an Artifact (§19.3.2) Classifiers; `roles.ts`
795
+ // declares those two flat, for the reasons it gives, and this list must
796
+ // not depend on that call going one way — a deployment diagram that
797
+ // stopped admitting artefacts because the vocabulary was re-parented
798
+ // would be a rule broken at a distance, by an edit that never mentioned
799
+ // it. Naming the three compartmented boxes says what the sheet refuses.
800
+ dep: [
801
+ UML_ROLE.actor,
802
+ UML_ROLE['use-case'],
803
+ UML_ROLE.subject,
804
+ UML_ROLE.object,
805
+ UML_ROLE.class,
806
+ UML_ROLE.interface,
807
+ UML_ROLE.enumeration,
808
+ ...UML_SEQUENCE_ROLES,
809
+ ],
810
+ // Phase 2 — the two BEHAVIOUR sheets, and the first pair whose deny-lists
811
+ // are mostly about EACH OTHER.
812
+ //
813
+ // An **activity** (§15.2.4) refuses the use case vocabulary, the instance,
814
+ // the classifiers and the structural artefacts, for the reasons every list
815
+ // above refuses them — and it refuses the STATE MACHINE vocabulary, which
816
+ // is the half that earns its place: §14.2.4 and §15.3.4 draw a rounded
817
+ // rectangle each and a diamond each, the two sheets sit next to each other
818
+ // in one menu, and a state drawn among actions is the confusion this pair
819
+ // of diagrams produces every time. `uml:pseudostate` reaches the choice,
820
+ // the junction, the two histories, the two connection points and the
821
+ // terminate cross in one entry.
822
+ //
823
+ // What it ADMITS is the whole activity vocabulary, the swimlanes, the
824
+ // notes and the packages — and the initial disc and the fork bar, which
825
+ // are NOT refused on either sheet because one role carries both notations
826
+ // (`roles.ts`).
827
+ act: [
828
+ UML_ROLE.actor,
829
+ UML_ROLE['use-case'],
830
+ UML_ROLE.subject,
831
+ UML_ROLE.object,
832
+ // The PARENT role: a class, an interface and an enumeration alike.
833
+ UML_ROLE.classifier,
834
+ UML_ROLE.component,
835
+ UML_ROLE.port,
836
+ UML_ROLE['provided-interface'],
837
+ UML_ROLE['required-interface'],
838
+ UML_ROLE.artifact,
839
+ // The PARENT role: a node, a device and an execution environment alike.
840
+ UML_ROLE.node,
841
+ // The state machine's own vocabulary — see above.
842
+ UML_ROLE.state,
843
+ UML_ROLE['final-state'],
844
+ UML_ROLE.pseudostate,
845
+ UML_ROLE.region,
846
+ ...UML_SEQUENCE_ROLES,
847
+ ],
848
+ // A **state machine** (§14.2.4) refuses the same strangers and the
849
+ // ACTIVITY vocabulary, named glyph by glyph rather than through
850
+ // `uml:control-node`: that parent covers the initial disc and the fork bar
851
+ // as well, and both of those are §14.2.4's own notation. A deny-list
852
+ // written on the parent would refuse the two artefacts a state machine
853
+ // most needs — which is exactly the rule broken at a distance the `dep`
854
+ // list above spells its three classifiers out to avoid.
855
+ //
856
+ // The DECISION is refused and the CHOICE is not, and the pair is the
857
+ // reason this list exists at all: they are the same diamond, one per
858
+ // sheet, and swapping them is one click in the morph menu.
859
+ stm: [
860
+ UML_ROLE.actor,
861
+ UML_ROLE['use-case'],
862
+ UML_ROLE.subject,
863
+ UML_ROLE.object,
864
+ UML_ROLE.classifier,
865
+ UML_ROLE.component,
866
+ UML_ROLE.port,
867
+ UML_ROLE['provided-interface'],
868
+ UML_ROLE['required-interface'],
869
+ UML_ROLE.artifact,
870
+ UML_ROLE.node,
871
+ // The activity's own vocabulary, one by one — see above.
872
+ UML_ROLE.action,
873
+ UML_ROLE['activity-final'],
874
+ UML_ROLE['flow-final'],
875
+ UML_ROLE.decision,
876
+ UML_ROLE['object-node'],
877
+ UML_ROLE['send-signal'],
878
+ UML_ROLE['accept-event'],
879
+ UML_ROLE['time-event'],
880
+ UML_ROLE.partition,
881
+ ...UML_SEQUENCE_ROLES,
882
+ ],
883
+ // Phase 3 — the SEQUENCE sheet (§17.2.4), and the STRICTEST list in the
884
+ // table by some distance.
885
+ //
886
+ // Every other kind here refuses a neighbouring vocabulary and admits its
887
+ // own plus whatever a whiteboard is made of. An `sd` frame refuses all
888
+ // EIGHT of the others, and the reason is the sheet's frame of reference
889
+ // rather than its vocabulary: a sequence diagram's vertical axis is TIME
890
+ // (§17.4.4 — "every line fragment is either horizontal or downwards when
891
+ // traversed from send event to receive event"), and an artefact that is
892
+ // not a participant has no position on it. A class dropped between two
893
+ // lifelines is not a class at the wrong level of detail, the way an
894
+ // instance on a package diagram is; it is a box the reader cannot date.
895
+ //
896
+ // So the list is every role the other lists name, plus the two behaviour
897
+ // vocabularies, plus the PACKAGE — which `act` and `stm` admit and this
898
+ // sheet does not, for the reason above. What is left is what §17 draws:
899
+ // the lifeline, the execution bar, the destruction cross, the combined
900
+ // fragment and the five message arrows — and the NOTE, which every sheet
901
+ // in this table admits because Annex A draws one on all of them.
902
+ //
903
+ // `uml:control-node` and `uml:pseudostate` are named as PARENTS here
904
+ // where the `act` and `stm` lists spell their children out. Those two
905
+ // lists had to: each sheet draws half the other's routing glyphs, so a
906
+ // deny-list on a parent would have refused the notation the author came
907
+ // for. This sheet draws none of them, which makes the parent the honest
908
+ // entry — and keeps the list from growing every time a routing glyph is
909
+ // added to a diagram this one has nothing to do with.
910
+ sd: [
911
+ UML_ROLE.actor,
912
+ UML_ROLE['use-case'],
913
+ UML_ROLE.subject,
914
+ UML_ROLE.object,
915
+ UML_ROLE.package,
916
+ // The PARENT role: a class, an interface and an enumeration alike.
917
+ UML_ROLE.classifier,
918
+ UML_ROLE.component,
919
+ UML_ROLE.port,
920
+ UML_ROLE['provided-interface'],
921
+ UML_ROLE['required-interface'],
922
+ UML_ROLE.artifact,
923
+ // The PARENT role: a node, a device and an execution environment alike.
924
+ UML_ROLE.node,
925
+ // The activity's vocabulary — the parent reaches the initial disc, the
926
+ // two finals, the decision and the fork bar in one entry.
927
+ UML_ROLE.action,
928
+ UML_ROLE['control-node'],
929
+ UML_ROLE['object-node'],
930
+ UML_ROLE['send-signal'],
931
+ UML_ROLE['accept-event'],
932
+ UML_ROLE['time-event'],
933
+ UML_ROLE.partition,
934
+ // …and the state machine's, the parent reaching the choice, the
935
+ // junction, the two histories, the two connection points and the
936
+ // terminate cross.
937
+ UML_ROLE.state,
938
+ UML_ROLE['final-state'],
939
+ UML_ROLE.pseudostate,
940
+ UML_ROLE.region,
941
+ ],
942
+ },
943
+ },
944
+ };
945
+ /**
946
+ * **U3** — a use case drawn outside every subject.
947
+ *
948
+ * §18.1.4 draws the SUBJECT as a rectangle round the use cases a system offers,
949
+ * with the actors outside it: the rectangle is what says "these are the things
950
+ * this system does", and a use case beside it is a behaviour attributed to
951
+ * nothing.
952
+ *
953
+ * ## `element-in-background`, which is the family that fits
954
+ *
955
+ * The brief asked for "outside every subject while one exists", and that is
956
+ * precisely what this family answers: a subject on the board is the frame, a use
957
+ * case not contained by ANY of them is the finding, and a board with no subject
958
+ * at all is silence. No polarity has to be turned round — the question is already
959
+ * the positive one — so the `element-in-zone` arrangement {@link actorInsideSubject}
960
+ * needs is not needed here.
961
+ *
962
+ * The silence is the half worth stating: a use case diagram whose author has not
963
+ * drawn the subject yet is a sketch, and most of them never draw one at all. The
964
+ * rule speaks only once somebody has declared where the system's boundary is.
965
+ */
966
+ const useCaseOutsideSubject = {
967
+ id: 'uml.use-case-outside-subject',
968
+ framework: 'uml',
969
+ family: 'element-in-background',
970
+ severity: 'audit',
971
+ appliesTo: UML_ROLE['use-case'],
972
+ roles: UML_ROLES,
973
+ messageKey: 'com.labre.uml.validation.use-case-outside-subject',
974
+ messageFallback: 'This use case sits outside the subject.',
975
+ suggestionKey: 'com.labre.uml.validation.use-case-outside-subject.suggestion',
976
+ suggestionFallback: 'The subject rectangle is what says which system offers these use cases. Move the ellipse inside the one it belongs to, or stretch the subject round it — the actors stay outside.',
977
+ version: 1,
978
+ provenance: {
979
+ source: 'recommendation',
980
+ reference: 'OMG UML 2.5.1 §18.1.4 — the subject rectangle encloses the UseCases the system offers, with the Actors outside it',
981
+ },
982
+ backgroundRole: UML_ROLE.subject,
983
+ };
984
+ /**
985
+ * **U4** — an actor drawn inside the subject.
986
+ *
987
+ * The other half of §18.1.4, and the one the notation is most explicit about: an
988
+ * actor is a role played by somebody OUTSIDE the system, and the subject
989
+ * rectangle is the system. A use case diagram whose stick figures are inside the
990
+ * box has erased the only line it had.
991
+ *
992
+ * ## The family is `element-in-zone`, and why
993
+ *
994
+ * The question is "is this element OUTSIDE that frame", and
995
+ * `element-in-background` cannot ask it: it has one polarity, INSIDE, with no
996
+ * `expect` to turn round. `element-in-zone` has the polarity, and the subject
997
+ * declares exactly one zone covering its whole plot (`background.ts` — it carries
998
+ * the subject's name and paints nothing), so citing that zone is citing the
999
+ * inside of the rectangle. Nothing is restated: the geometry comes from the very
1000
+ * declaration the renderer paints from, so a subject that is moved, resized or
1001
+ * re-margined moves this rule with it. C4's `person-in-boundary` is the same
1002
+ * arrangement for the same reason, one framework over.
1003
+ *
1004
+ * `element-in-zone` judges a subject only against the frame that CONTAINS it, so
1005
+ * an actor straddling the edge — half in, half out — raises nothing: only an
1006
+ * actor drawn wholly inside is an actor the author has put inside the system.
1007
+ */
1008
+ const actorInsideSubject = {
1009
+ id: 'uml.actor-inside-subject',
1010
+ framework: 'uml',
1011
+ family: 'element-in-zone',
1012
+ severity: 'audit',
1013
+ appliesTo: UML_ROLE.actor,
1014
+ roles: UML_ROLES,
1015
+ messageKey: 'com.labre.uml.validation.actor-inside-subject',
1016
+ messageFallback: 'This actor is drawn inside the subject.',
1017
+ suggestionKey: 'com.labre.uml.validation.actor-inside-subject.suggestion',
1018
+ suggestionFallback: 'An actor is a role played from outside the system, and the subject rectangle is the system. Move the figure out and let the association cross the edge.',
1019
+ version: 1,
1020
+ provenance: {
1021
+ source: 'recommendation',
1022
+ reference: 'OMG UML 2.5.1 §18.1.3 — an Actor models a role played by an entity external to the subject',
1023
+ },
1024
+ backgroundRole: UML_ROLE.subject,
1025
+ // The frame's own declaration, carried as data exactly like `roles` is: it is
1026
+ // where the plot and its one zone are written, and the engine resolves the
1027
+ // rectangle from it rather than knowing anything about UML.
1028
+ background: UML_SUBJECT_FRAME,
1029
+ inZone: {
1030
+ // The subject's single full-plot zone — see the header. It exists to carry
1031
+ // the subject's name and paints nothing, so it IS the inside of the frame.
1032
+ zoneIds: ['name'],
1033
+ expect: 'outside',
1034
+ },
1035
+ };
1036
+ /* ── Naming: does the drawing say anything at all? ──────────────────────── */
1037
+ /**
1038
+ * **U5** — a name compartment somebody has emptied.
1039
+ *
1040
+ * Written on `uml:name`, the TEXT role, and never on the artefact: since a UML
1041
+ * node became a group the shape carries no words at all, so a rule written on
1042
+ * `uml:class` would read nothing on every box on the sheet and report every one
1043
+ * of them unnamed. The same move C4's `unnamed-element` makes, and for the same
1044
+ * reason.
1045
+ *
1046
+ * ## What it actually fires on, which is narrower than it sounds
1047
+ *
1048
+ * Every tier is SEEDED at creation (`actions.ts`: `«interface»\nInterface`,
1049
+ * `Package`, `+ attribute : Type`), so a fresh artefact is prompted rather than
1050
+ * nameless and `label-presence` sees words. The rule therefore fires on exactly
1051
+ * one thing: a name compartment an author has emptied.
1052
+ *
1053
+ * ## It reaches the package and the note too, and that is the honest reading
1054
+ *
1055
+ * `uml:name` is the name compartment of the classifiers AND of the object, and it
1056
+ * is also the package's single line and the note's body (`roles.ts` says why the
1057
+ * two share the role: a package name is a namespace and a note's text is what the
1058
+ * exporter writes as the comment's body — both are NAMES in the sense the
1059
+ * grammar means, where an actor's one word is a `uml:label`). So an emptied
1060
+ * package and an emptied note are findings here, under one sentence that fits
1061
+ * all four: the drawing has a box and no words.
1062
+ *
1063
+ * ## The known limit: a DELETED compartment is silence
1064
+ *
1065
+ * If the text element is removed outright rather than emptied there is no
1066
+ * `uml:name` on the sheet for this rule to be about. Closing it means asking
1067
+ * whether a GROUP has a name among its children, which is a question about
1068
+ * membership and not about a label's presence, and no family expresses it today.
1069
+ * `umlComponentOf` (`component.ts`) is where the answer would come from.
1070
+ */
1071
+ const unnamedClassifier = {
1072
+ id: 'uml.unnamed-classifier',
1073
+ framework: 'uml',
1074
+ family: 'label-presence',
1075
+ severity: 'audit',
1076
+ appliesTo: UML_ROLE.name,
1077
+ roles: UML_ROLES,
1078
+ messageKey: 'com.labre.uml.validation.unnamed-classifier',
1079
+ messageFallback: 'This artefact has no name.',
1080
+ suggestionKey: 'com.labre.uml.validation.unnamed-classifier.suggestion',
1081
+ suggestionFallback: 'Write the name the rest of the model refers to it by — "Order", "«interface» Payable", "o1 : Order". The compartments below already say what it has and what it does, so the name is free to say which one it is.',
1082
+ version: 1,
1083
+ provenance: {
1084
+ source: 'recommendation',
1085
+ reference: 'OMG UML 2.5.1 §9.2.4 — a Classifier is drawn as a rectangle with its name in the top compartment',
1086
+ },
1087
+ // Explicitly on-demand, and it stays explicit: `moment: undefined` means
1088
+ // REALTIME, and the engine watches `text` for exactly this family when a
1089
+ // real-time rule of it is registered. Dropping this line would hand the
1090
+ // drawing path a debounced re-evaluation per keystroke, in every compartment.
1091
+ moment: 'on-demand',
1092
+ // Attribution only — the check reads no geometry — so an arbitration made on
1093
+ // one sheet covers that sheet and no other.
1094
+ backgroundRole: UML_ROLE.diagram,
1095
+ label: { present: true },
1096
+ };
1097
+ /**
1098
+ * **U6** — an actor's or a use case's one word, emptied.
1099
+ *
1100
+ * ONE rule for the two artefacts, and the brief asked for two ids. They cannot
1101
+ * be two rules, and the reason is in `roles.ts`: an actor and a use case carry
1102
+ * the SAME tier role, `uml:label` — "the same tier, named differently because it
1103
+ * is not a compartment" — so two `label-presence` rules would name the same
1104
+ * subject role, fire on the same emptied text and put two brackets and two
1105
+ * sentences on one word to fix, one of which would always be about the wrong
1106
+ * shape. `label-presence` reads the subject's own words and cannot see what the
1107
+ * text is grouped WITH.
1108
+ *
1109
+ * So the SUBJECT is the tier rather than either shape — `appliesTo` is
1110
+ * `UML_ROLE.label` and says so — the sentence fits both, and the day a family
1111
+ * can ask "which kind of artefact is this text grouped with" the rule splits in
1112
+ * two with no data to migrate.
1113
+ *
1114
+ * The ID does not say that, and it is stuck not saying it: `uml.unnamed-label`
1115
+ * is the name this rule should carry, and the id is frozen because documents
1116
+ * store it (see the "what this pack does NOT report" note above). The tier is
1117
+ * therefore stated here and in `appliesTo`, which is where a reader who needs
1118
+ * it will look.
1119
+ */
1120
+ const unnamedActorOrUseCase = {
1121
+ id: 'uml.unnamed-actor-or-use-case',
1122
+ framework: 'uml',
1123
+ family: 'label-presence',
1124
+ severity: 'audit',
1125
+ appliesTo: UML_ROLE.label,
1126
+ roles: UML_ROLES,
1127
+ messageKey: 'com.labre.uml.validation.unnamed-actor-or-use-case',
1128
+ messageFallback: 'This actor or use case has no name.',
1129
+ suggestionKey: 'com.labre.uml.validation.unnamed-actor-or-use-case.suggestion',
1130
+ suggestionFallback: 'Name the role or the behaviour in the reader’s own words — "Customer", "Place an order". A use case diagram is read as sentences, and an unnamed ellipse leaves one of them half written.',
1131
+ version: 1,
1132
+ provenance: {
1133
+ source: 'recommendation',
1134
+ reference: 'OMG UML 2.5.1 §18.1.4 — a UseCase is drawn as an ellipse carrying its name, an Actor as a stick figure with its name beneath',
1135
+ },
1136
+ moment: 'on-demand',
1137
+ backgroundRole: UML_ROLE.diagram,
1138
+ label: { present: true },
1139
+ };
1140
+ /* ── Spelling: are the words written the way the clause writes them? ─────── */
1141
+ /**
1142
+ * **U35–U38** — the four `label-syntax` rules, and the first rules in this
1143
+ * library whose verdict is a PARSER rather than a table.
1144
+ *
1145
+ * `label-presence` asks whether a compartment says anything; these ask whether
1146
+ * what it says parses. The four clauses are the four the pack already reads for
1147
+ * its exporters — §9.5.4 (a Property), §9.6.4 (an Operation), §14.2.4.8 (a
1148
+ * transition label) and §7.5.4 (a multiplicity range) — and the checkers are
1149
+ * `grammar.ts`'s own, so a finding here is exactly the line that will export
1150
+ * short. That is the strongest claim any rule in this library makes: the tool
1151
+ * and the file agree by construction, because they read the same function.
1152
+ *
1153
+ * ## Why they are `standard`, and what keeps that honest
1154
+ *
1155
+ * Each restates a BNF the specification prints. What would make the claim
1156
+ * dishonest is a checker stricter than the clause — "an attribute must carry a
1157
+ * type" indicts `balance`, which §9.5.4 permits — so `grammar.ts` states the one
1158
+ * rule its checkers follow and no rule here is allowed to add to it: a line is
1159
+ * reported only when the lenient parse LOST something the author wrote.
1160
+ *
1161
+ * ## Why they are `on-demand`, explicitly
1162
+ *
1163
+ * The same reason {@link unnamedClassifier} is, one order of magnitude worse.
1164
+ * These read WORDS, which a user changes by typing, and they read all of them:
1165
+ * a class's attribute compartment is parsed line by line. Real-time, every
1166
+ * keystroke in every compartment on the sheet would wake the debounced pass and
1167
+ * re-run four parsers. `audit` already implies the moment (PF7.6); the line is
1168
+ * written anyway, because `moment: undefined` means REALTIME and a later change
1169
+ * of severity must not silently move these onto the gesture path.
1170
+ */
1171
+ const attributeSyntax = {
1172
+ id: 'uml.attribute-syntax',
1173
+ framework: 'uml',
1174
+ family: 'label-syntax',
1175
+ severity: 'audit',
1176
+ appliesTo: UML_ROLE.attributes,
1177
+ roles: UML_ROLES,
1178
+ messageKey: 'com.labre.uml.validation.attribute-syntax',
1179
+ messageFallback: 'This line is not an attribute:',
1180
+ suggestionKey: 'com.labre.uml.validation.attribute-syntax.suggestion',
1181
+ suggestionFallback: 'An attribute is written "- balance : Money [0..1] = 0 {readOnly}", and everything after the name is optional. A line with parentheses is an operation and belongs in the compartment below.',
1182
+ version: 1,
1183
+ provenance: {
1184
+ source: 'standard',
1185
+ reference: 'OMG UML 2.5.1 §9.5.4 — [<visibility>] [/] <name> [: <prop-type>] [[<multiplicity>]] [= <default>] [{<modifiers>}]',
1186
+ },
1187
+ moment: 'on-demand',
1188
+ backgroundRole: UML_ROLE.diagram,
1189
+ labelSyntax: { parse: checkPropertyLine },
1190
+ };
1191
+ const operationSyntax = {
1192
+ id: 'uml.operation-syntax',
1193
+ framework: 'uml',
1194
+ family: 'label-syntax',
1195
+ severity: 'audit',
1196
+ appliesTo: UML_ROLE.operations,
1197
+ roles: UML_ROLES,
1198
+ messageKey: 'com.labre.uml.validation.operation-syntax',
1199
+ messageFallback: 'This line is not an operation:',
1200
+ suggestionKey: 'com.labre.uml.validation.operation-syntax.suggestion',
1201
+ suggestionFallback: 'An operation is written "+ place(order : Order) : Receipt", and the parentheses are what make it one — an operation with no parameters still has them.',
1202
+ version: 1,
1203
+ provenance: {
1204
+ source: 'standard',
1205
+ reference: 'OMG UML 2.5.1 §9.6.4 — [<visibility>] <name> ( [<parameter-list>] ) [: [<return-type>] [[<multiplicity>]] [{<oper-property>*}]]',
1206
+ },
1207
+ moment: 'on-demand',
1208
+ backgroundRole: UML_ROLE.diagram,
1209
+ labelSyntax: { parse: checkOperationLine },
1210
+ };
1211
+ /**
1212
+ * The transition's CENTRE label, read as ONE expression.
1213
+ *
1214
+ * `perLine: false`, unlike the two compartments above, and the clause is the
1215
+ * reason: §14.2.4.8's label is a single production — `trigger [guard] / effect` —
1216
+ * where §9.5.4's compartment is a list. An author who wraps a long label onto a
1217
+ * second line has written one label, and judging the halves separately would
1218
+ * report a guard with no trigger and a trigger with no guard for a label that is
1219
+ * neither.
1220
+ */
1221
+ const transitionSyntax = {
1222
+ id: 'uml.transition-syntax',
1223
+ framework: 'uml',
1224
+ family: 'label-syntax',
1225
+ severity: 'audit',
1226
+ appliesTo: UML_ROLE.transition,
1227
+ roles: UML_ROLES,
1228
+ messageKey: 'com.labre.uml.validation.transition-syntax',
1229
+ messageFallback: 'This transition label does not parse:',
1230
+ suggestionKey: 'com.labre.uml.validation.transition-syntax.suggestion',
1231
+ suggestionFallback: 'A transition is labelled "trigger [guard] / effect", and every part of it is optional — a transition that fires when its source state finishes carries no label at all.',
1232
+ version: 1,
1233
+ provenance: {
1234
+ source: 'standard',
1235
+ reference: 'OMG UML 2.5.1 §14.2.4.8 — [<trigger> [, <trigger>]*] [[<guard>]] [/ <behavior-expression>]',
1236
+ },
1237
+ moment: 'on-demand',
1238
+ backgroundRole: UML_ROLE.diagram,
1239
+ labelSyntax: { parse: checkTransitionLabel, perLine: false },
1240
+ };
1241
+ /**
1242
+ * The multiplicity written at an association's ENDS — both of them, one rule.
1243
+ *
1244
+ * `target: 'end-labels'` reads the two end labels as two lines of one subject
1245
+ * (`validation.ts` {@link LabelTarget}), which is what lets one id cover a
1246
+ * grammar §11.5.4 places at both ends. Two rules would be two sentences and two
1247
+ * lines in every profile table for one requirement, and each could only ever
1248
+ * check half of every line drawn.
1249
+ *
1250
+ * `appliesTo: uml:association` reaches aggregation and composition for free —
1251
+ * they specialise it (`roles.ts`: §11.5.4's aggregation kind is a property of an
1252
+ * association END, not a different relationship).
1253
+ *
1254
+ * ## It is silent about the ROLE name, and that is the whole design
1255
+ *
1256
+ * An end label is a multiplicity, a role name, a visibility marker, or any
1257
+ * mixture of the three with no separator between them (§11.5.4). Only the range
1258
+ * has a syntax to be wrong about, so `checkEndLabelMultiplicity` fires on a
1259
+ * leading token that is unambiguously an attempt at one — `1..n`, `0...*` — and
1260
+ * leaves `items`, `- owner` and even `1st choice` alone. A checker stricter than
1261
+ * `parseEndLabel` would indict the ends the exporters read correctly.
1262
+ */
1263
+ const multiplicitySyntax = {
1264
+ id: 'uml.multiplicity-syntax',
1265
+ framework: 'uml',
1266
+ family: 'label-syntax',
1267
+ severity: 'audit',
1268
+ appliesTo: UML_ROLE.association,
1269
+ roles: UML_ROLES,
1270
+ messageKey: 'com.labre.uml.validation.multiplicity-syntax',
1271
+ messageFallback: 'This end of the association is not a multiplicity:',
1272
+ suggestionKey: 'com.labre.uml.validation.multiplicity-syntax.suggestion',
1273
+ suggestionFallback: 'A range is written "1", "0..1", "0..*" or "*", with whole numbers at both bounds. A bound the file cannot hold — "1..n" — travels as part of the end’s name instead of as a range.',
1274
+ version: 1,
1275
+ provenance: {
1276
+ source: 'standard',
1277
+ reference: 'OMG UML 2.5.1 §7.5.4 — [<lower> ..] <upper>, read at an association end per §11.5.4',
1278
+ },
1279
+ moment: 'on-demand',
1280
+ backgroundRole: UML_ROLE.diagram,
1281
+ labelSyntax: {
1282
+ parse: checkEndLabelMultiplicity,
1283
+ target: 'end-labels',
1284
+ },
1285
+ };
1286
+ /* ── Grammar: what each line may run between ────────────────────────────── */
1287
+ /**
1288
+ * **U7** — a generalization whose two ends are not of the same kind.
1289
+ *
1290
+ * §9.9.7 is normative and short: the general classifier is of the same kind as
1291
+ * the specific one. A hollow triangle from a class to an interface is a
1292
+ * REALIZATION drawn with the wrong tool (`morph.ts` turns one into the other in a
1293
+ * click); one from a use case to an actor is two notations crossed; one pointing
1294
+ * at an instance is a generalization drawn onto a runtime value, which is why
1295
+ * `uml:object` is in this rule's alphabet and in none of its sentences.
1296
+ *
1297
+ * The four sanctioned sentences are the diagonal — class → class, interface →
1298
+ * interface, actor → actor, use case → use case — and everything else in the
1299
+ * alphabet is off it. See {@link UML_GENERALIZATION_MATRIX} for why the fifth
1300
+ * triplet carries a different edge role, and the file header for why that device
1301
+ * is what gives this rule anything to say at all.
1302
+ *
1303
+ * ## The self-loop is NOT here, and that is the point
1304
+ *
1305
+ * {@link generalizationSelfLoop} carries it, because the two clauses have
1306
+ * different AUTHORITY — the same split BPMN and C4 both make — and because the
1307
+ * family `continue`s on a loop before reaching any matrix, so exactly one of the
1308
+ * two can indict a given edge.
1309
+ */
1310
+ const generalizationEndpoints = {
1311
+ id: 'uml.generalization-endpoints',
1312
+ framework: 'uml',
1313
+ family: 'relation-endpoints',
1314
+ severity: 'audit',
1315
+ // No `appliesTo`: the subject is a RELATION, and the role that names it is
1316
+ // declared where the family reads it.
1317
+ roles: UML_ROLES,
1318
+ messageKey: 'com.labre.uml.validation.generalization-endpoints',
1319
+ messageFallback: 'This generalization runs between two artefacts of different kinds.',
1320
+ suggestionKey: 'com.labre.uml.validation.generalization-endpoints.suggestion',
1321
+ suggestionFallback: 'A class specialises a class, an interface an interface, an actor an actor, a use case a use case. If what you mean is "this class implements that interface", draw a realization; if the far end is an instance, point at its classifier instead.',
1322
+ version: 1,
1323
+ provenance: {
1324
+ source: 'standard',
1325
+ reference: 'OMG UML 2.5.1 §9.9.7 — the general Classifier must be of the same kind as the specific Classifier',
1326
+ },
1327
+ backgroundRole: UML_ROLE.diagram,
1328
+ endpoints: {
1329
+ edgeRole: UML_ROLE.generalization,
1330
+ allowed: UML_GENERALIZATION_MATRIX,
1331
+ // No `forbidSelfLoop`: that clause is `uml.generalization-self-loop`, and its
1332
+ // absence here is what lets exactly one of the two indict a given loop.
1333
+ },
1334
+ };
1335
+ /**
1336
+ * **U8** — a generalization looped back onto the artefact it leaves.
1337
+ *
1338
+ * §9.9.7 again, and its other constraint: a generalization hierarchy is directed
1339
+ * and ACYCLIC, so a classifier cannot be a transitively general and a
1340
+ * transitively specific classifier of itself. The one-hop cycle is the case a
1341
+ * drawing can hold and a rule can see — the longer ones are a `reachability`
1342
+ * question this pack does not ask — and it is almost always a hand that dropped
1343
+ * the second end on the first.
1344
+ *
1345
+ * ## Why the two rules compose safely
1346
+ *
1347
+ * The ALPHABET GATE runs first, so a generalization looped onto a note, a package
1348
+ * or a frame is silence from both rules. Past the gate the family tests the
1349
+ * self-loop FIRST and `continue`s unconditionally — the `continue` sits outside
1350
+ * the `forbidSelfLoop` guard — so a loop never reaches a matrix at all. This rule
1351
+ * therefore declares the PERMISSIVE {@link UML_GENERALIZATION_ALPHABET} rather
1352
+ * than U7's grammar: carrying the grammar would report every wrong sentence a
1353
+ * second time on every non-loop generalization, which is exactly the trap C4
1354
+ * documents and its suite caught twice.
1355
+ *
1356
+ * It carries no `selfLoop` block: with the matrix unreachable here, the rule's
1357
+ * own message IS the self-loop message and the family falls back to it.
1358
+ */
1359
+ const generalizationSelfLoop = {
1360
+ id: 'uml.generalization-self-loop',
1361
+ framework: 'uml',
1362
+ family: 'relation-endpoints',
1363
+ severity: 'audit',
1364
+ roles: UML_ROLES,
1365
+ messageKey: 'com.labre.uml.validation.generalization-self-loop',
1366
+ messageFallback: 'This generalization loops back onto its own classifier.',
1367
+ suggestionKey: 'com.labre.uml.validation.generalization-self-loop.suggestion',
1368
+ suggestionFallback: 'Nothing is a specialisation of itself: a generalization hierarchy is directed and acyclic. Point the triangle at the classifier this one really specialises, or delete the line.',
1369
+ version: 1,
1370
+ provenance: {
1371
+ source: 'standard',
1372
+ reference: 'OMG UML 2.5.1 §9.9.7 — Generalization hierarchies must be directed and acyclical',
1373
+ },
1374
+ backgroundRole: UML_ROLE.diagram,
1375
+ endpoints: {
1376
+ edgeRole: UML_ROLE.generalization,
1377
+ // The ALPHABET, never U7's grammar — see the header.
1378
+ allowed: UML_GENERALIZATION_ALPHABET,
1379
+ forbidSelfLoop: true,
1380
+ },
1381
+ };
1382
+ /**
1383
+ * **U9** — a realization that does not point at an interface.
1384
+ *
1385
+ * §10.4.3 types an InterfaceRealization's `contract` end as an Interface, and the
1386
+ * notation draws it as the dashed line with the hollow triangle. A realization
1387
+ * between two classes is a generalization drawn with the wrong tool — the two
1388
+ * differ by a dash, which is the single easiest mistake to make on a class
1389
+ * diagram and the single easiest to fix (`morph.ts`).
1390
+ *
1391
+ * One sentence, one alphabet of one role, and therefore exactly one thing this
1392
+ * rule can ever say. See {@link UML_REALIZATION_MATRIX} for what it declines to
1393
+ * have an opinion about.
1394
+ */
1395
+ const realizationEndpoints = {
1396
+ id: 'uml.realization-endpoints',
1397
+ framework: 'uml',
1398
+ family: 'relation-endpoints',
1399
+ severity: 'audit',
1400
+ roles: UML_ROLES,
1401
+ messageKey: 'com.labre.uml.validation.realization-endpoints',
1402
+ messageFallback: 'This realization does not point at an interface.',
1403
+ suggestionKey: 'com.labre.uml.validation.realization-endpoints.suggestion',
1404
+ suggestionFallback: 'A realization says "this classifier implements that contract", and a contract is an interface. Point the dashed triangle at one — or, if what you mean is "is a", draw a generalization instead.',
1405
+ version: 1,
1406
+ provenance: {
1407
+ source: 'standard',
1408
+ reference: 'OMG UML 2.5.1 §10.4.3 — an InterfaceRealization’s contract is an Interface',
1409
+ },
1410
+ backgroundRole: UML_ROLE.diagram,
1411
+ endpoints: {
1412
+ edgeRole: UML_ROLE.realization,
1413
+ allowed: UML_REALIZATION_MATRIX,
1414
+ },
1415
+ };
1416
+ /**
1417
+ * **U10** — a dependency pointing at an instance.
1418
+ *
1419
+ * §9.8.3: an InstanceSpecification illustrates the model, it is not part of it.
1420
+ * A dashed arrow from a class to `o1 : Order` is a statement about a value where
1421
+ * the author meant a statement about a type, and the fix is one drag.
1422
+ *
1423
+ * The object is a legal SOURCE — a diagram may show what an instance needs — and
1424
+ * never a legal target, which is the whole of {@link UML_DEPENDENCY_MATRIX}.
1425
+ *
1426
+ * ## What the brief asked for that this rule no longer carries
1427
+ *
1428
+ * "dependency OR generalization to `uml:object`". `relation-endpoints` names one
1429
+ * edge role, so the generalization half cannot live here — and it does not have
1430
+ * to: {@link generalizationEndpoints} already indicts a generalization pointing at
1431
+ * an instance, because `uml:object` is in its alphabet and in none of its
1432
+ * sentences. One verdict per wrong edge, and each carries the sentence that fits
1433
+ * the line the author actually drew.
1434
+ */
1435
+ const dependencyOnObject = {
1436
+ id: 'uml.dependency-on-object',
1437
+ framework: 'uml',
1438
+ family: 'relation-endpoints',
1439
+ severity: 'audit',
1440
+ roles: UML_ROLES,
1441
+ messageKey: 'com.labre.uml.validation.dependency-on-object',
1442
+ messageFallback: 'This dependency points at an instance.',
1443
+ suggestionKey: 'com.labre.uml.validation.dependency-on-object.suggestion',
1444
+ suggestionFallback: 'A dependency is a statement about the model, and an instance is an illustration of it. Point the arrow at the classifier the instance is of, or at the package that holds it.',
1445
+ version: 1,
1446
+ provenance: {
1447
+ source: 'recommendation',
1448
+ reference: 'OMG UML 2.5.1 §9.8.3 — an InstanceSpecification illustrates a Classifier; the model’s dependencies are stated between the named elements themselves',
1449
+ },
1450
+ backgroundRole: UML_ROLE.diagram,
1451
+ endpoints: {
1452
+ edgeRole: UML_ROLE.dependency,
1453
+ allowed: UML_DEPENDENCY_MATRIX,
1454
+ },
1455
+ };
1456
+ /**
1457
+ * **U11** — an include with an end that is not a use case.
1458
+ *
1459
+ * §18.1.3 types both ends of an Include as UseCases: an inclusion says "running
1460
+ * this behaviour always runs that one", which is a statement about two
1461
+ * behaviours. An include drawn from an ACTOR — the mistake this rule exists for
1462
+ * — is somebody reaching for the association tool's neighbour in the menu.
1463
+ *
1464
+ * Reads the use case grammar's `uml:include` slice; the two association triplets
1465
+ * in that table are the alphabet entries that make an actor end a finding rather
1466
+ * than silence (see {@link UML_USE_CASE_MATRIX}).
1467
+ */
1468
+ const includeEndpoints = {
1469
+ id: 'uml.include-endpoints',
1470
+ framework: 'uml',
1471
+ family: 'relation-endpoints',
1472
+ severity: 'audit',
1473
+ roles: UML_ROLES,
1474
+ messageKey: 'com.labre.uml.validation.include-endpoints',
1475
+ messageFallback: 'This include runs between something other than two use cases.',
1476
+ suggestionKey: 'com.labre.uml.validation.include-endpoints.suggestion',
1477
+ suggestionFallback: 'An include says one behaviour always runs another, so both ends are use cases. If the end is an actor, what you want is an association — draw that instead.',
1478
+ version: 1,
1479
+ provenance: {
1480
+ source: 'standard',
1481
+ reference: 'OMG UML 2.5.1 §18.1.3 — an Include relates an including UseCase to the UseCase it includes',
1482
+ },
1483
+ backgroundRole: UML_ROLE.diagram,
1484
+ endpoints: {
1485
+ edgeRole: UML_ROLE.include,
1486
+ allowed: UML_USE_CASE_MATRIX,
1487
+ },
1488
+ };
1489
+ /**
1490
+ * **U12** — an extend with an end that is not a use case.
1491
+ *
1492
+ * {@link includeEndpoints}' twin, on §18.1.3's other relationship: an Extend
1493
+ * relates the extending UseCase to the extended one. The two are declared
1494
+ * separately because the roles are flat — an extend is not a kind of include, and
1495
+ * one rule policing both would be a rule nobody could read the verdict of.
1496
+ */
1497
+ const extendEndpoints = {
1498
+ id: 'uml.extend-endpoints',
1499
+ framework: 'uml',
1500
+ family: 'relation-endpoints',
1501
+ severity: 'audit',
1502
+ roles: UML_ROLES,
1503
+ messageKey: 'com.labre.uml.validation.extend-endpoints',
1504
+ messageFallback: 'This extend runs between something other than two use cases.',
1505
+ suggestionKey: 'com.labre.uml.validation.extend-endpoints.suggestion',
1506
+ suggestionFallback: 'An extend says one behaviour may add itself to another at a point the other declares, so both ends are use cases. If the end is an actor, draw an association instead.',
1507
+ version: 1,
1508
+ provenance: {
1509
+ source: 'standard',
1510
+ reference: 'OMG UML 2.5.1 §18.1.3 — an Extend relates an extending UseCase to the extended UseCase',
1511
+ },
1512
+ backgroundRole: UML_ROLE.diagram,
1513
+ endpoints: {
1514
+ edgeRole: UML_ROLE.extend,
1515
+ allowed: UML_USE_CASE_MATRIX,
1516
+ },
1517
+ };
1518
+ /**
1519
+ * **U13** — an association drawn between two actors.
1520
+ *
1521
+ * §18.1.3 lists what an Actor may be associated with — UseCases, Components and
1522
+ * Classes — and two actors is not on it. Two stick figures joined by a solid line
1523
+ * is almost always an organisation chart drawn on a use case diagram: the
1524
+ * relationship between two roles is real and important and it is not what this
1525
+ * notation draws. (A GENERALIZATION between two actors is legal and this rule
1526
+ * says nothing about one — it reads `uml:association` alone.)
1527
+ *
1528
+ * ## The one removal, and the reason the table is written as one
1529
+ *
1530
+ * {@link UML_ASSOCIATION_MATRIX} is the alphabet minus a single line, exactly as
1531
+ * C4's relationship grammar is. That is what makes this a single-verdict rule:
1532
+ * everything else in the alphabet is sanctioned, so the only sentence it can
1533
+ * report is the one its message names. An association onto a package or a note
1534
+ * is outside the alphabet and stays outside the conversation.
1535
+ *
1536
+ * Also the rule that reaches the two DIAMONDS for free: `uml:aggregation` and
1537
+ * `uml:composition` specialise `uml:association` (§11.5.4), so `roleIsA` brings
1538
+ * them in and the same grammar judges all three.
1539
+ */
1540
+ const actorActorAssociation = {
1541
+ id: 'uml.actor-actor-association',
1542
+ framework: 'uml',
1543
+ family: 'relation-endpoints',
1544
+ severity: 'audit',
1545
+ roles: UML_ROLES,
1546
+ messageKey: 'com.labre.uml.validation.actor-actor-association',
1547
+ messageFallback: 'This association runs between two actors.',
1548
+ suggestionKey: 'com.labre.uml.validation.actor-actor-association.suggestion',
1549
+ suggestionFallback: 'A use case diagram associates an actor with what the system does for them, never with another actor. Draw what each of them uses — or, if one role is a kind of the other, draw a generalization.',
1550
+ version: 1,
1551
+ provenance: {
1552
+ source: 'standard',
1553
+ reference: 'OMG UML 2.5.1 §18.1.3 — an Actor may have Associations with UseCases, Components and Classes, and with nothing else',
1554
+ },
1555
+ backgroundRole: UML_ROLE.diagram,
1556
+ endpoints: {
1557
+ edgeRole: UML_ROLE.association,
1558
+ allowed: UML_ASSOCIATION_MATRIX,
1559
+ },
1560
+ };
1561
+ /**
1562
+ * **U17** — a `«deploy»` arrow that does not put an artefact on a node.
1563
+ *
1564
+ * §19.2.3 types the two ends of a Deployment — a DeployedArtifact onto a
1565
+ * DeploymentTarget — and §19.4.4 draws it as the dashed arrow with the keyword.
1566
+ * The one drawing this rule is really for is the arrow from a COMPONENT to a
1567
+ * node: it is the sentence everybody means and the one §19 declines to draw,
1568
+ * because what reaches a machine is the artefact — the jar, the image, the
1569
+ * script — and the component is what that artefact MANIFESTS (§19.3.3). The fix
1570
+ * is one box and two arrows, and the suggestion says so.
1571
+ *
1572
+ * Reads {@link UML_DEPLOY_MATRIX}, whose second triplet is the alphabet entry
1573
+ * that makes the component end a finding rather than silence.
1574
+ */
1575
+ const deployEndpoints = {
1576
+ id: 'uml.deploy-endpoints',
1577
+ framework: 'uml',
1578
+ family: 'relation-endpoints',
1579
+ severity: 'audit',
1580
+ roles: UML_ROLES,
1581
+ messageKey: 'com.labre.uml.validation.deploy-endpoints',
1582
+ messageFallback: 'This deployment does not put an artefact on a node.',
1583
+ suggestionKey: 'com.labre.uml.validation.deploy-endpoints.suggestion',
1584
+ suggestionFallback: 'A deployment runs from the artefact — the jar, the image, the script — to the node, device or execution environment it is installed on. If the far end of this arrow is a component, draw the artefact that manifests it and deploy that instead.',
1585
+ version: 1,
1586
+ provenance: {
1587
+ source: 'standard',
1588
+ reference: 'OMG UML 2.5.1 §19.2.3 — a Deployment relates a DeployedArtifact to the DeploymentTarget it is deployed on; §19.4.4 draws it as the «deploy» arrow',
1589
+ },
1590
+ backgroundRole: UML_ROLE.diagram,
1591
+ endpoints: {
1592
+ edgeRole: UML_ROLE.deploy,
1593
+ allowed: UML_DEPLOY_MATRIX,
1594
+ },
1595
+ };
1596
+ /**
1597
+ * **U18** — a `«manifest»` arrow that does not run from an artefact to a
1598
+ * component.
1599
+ *
1600
+ * §19.3.3: the Artifact owns the Manifestations, each representing the
1601
+ * utilization of some model element, and §19.3.5's figure draws the one this
1602
+ * notation is used for — an artefact and the component it embodies. An arrow
1603
+ * drawn the other way round, or one aimed at the NODE the artefact happens to
1604
+ * sit on, is the deployment story told with the wrong word.
1605
+ *
1606
+ * {@link deployEndpoints}' twin, and the two never double-report: an edge
1607
+ * carries one role, `uml:deploy` and `uml:manifest` are flat siblings, and each
1608
+ * rule reads its own.
1609
+ */
1610
+ const manifestEndpoints = {
1611
+ id: 'uml.manifest-endpoints',
1612
+ framework: 'uml',
1613
+ family: 'relation-endpoints',
1614
+ severity: 'audit',
1615
+ roles: UML_ROLES,
1616
+ messageKey: 'com.labre.uml.validation.manifest-endpoints',
1617
+ messageFallback: 'This manifestation does not run from an artefact to a component.',
1618
+ suggestionKey: 'com.labre.uml.validation.manifest-endpoints.suggestion',
1619
+ suggestionFallback: 'A manifestation says "this file embodies that component", so it leaves the artefact and arrives on the component. If what you meant is where the file is installed, draw a deployment to the node instead.',
1620
+ version: 1,
1621
+ provenance: {
1622
+ source: 'standard',
1623
+ reference: 'OMG UML 2.5.1 §19.3.3 — an Artifact owns its Manifestations, each the utilization of a PackageableElement; §19.3.4 draws it as the «manifest» arrow',
1624
+ },
1625
+ backgroundRole: UML_ROLE.diagram,
1626
+ endpoints: {
1627
+ edgeRole: UML_ROLE.manifest,
1628
+ allowed: UML_MANIFEST_MATRIX,
1629
+ },
1630
+ };
1631
+ /**
1632
+ * **U19** — a communication path drawn to something that is not a node.
1633
+ *
1634
+ * §19.4.3, and one of the few clauses in the whole specification that types a
1635
+ * relationship in a single sentence: a CommunicationPath is an Association
1636
+ * between two DeploymentTargets. A network line drawn to a `.jar` or to a
1637
+ * component box is the confusion this rule is for — the SOFTWARE does not talk
1638
+ * to a machine, the machine it runs on does, and the drawing that says otherwise
1639
+ * has skipped the node.
1640
+ *
1641
+ * Both ends are `uml:node`, so a device connected to an execution environment is
1642
+ * sanctioned through the parent role and never mentioned.
1643
+ */
1644
+ const communicationPathEndpoints = {
1645
+ id: 'uml.communication-path-endpoints',
1646
+ framework: 'uml',
1647
+ family: 'relation-endpoints',
1648
+ severity: 'audit',
1649
+ roles: UML_ROLES,
1650
+ messageKey: 'com.labre.uml.validation.communication-path-endpoints',
1651
+ messageFallback: 'This communication path has an end that is not a node.',
1652
+ suggestionKey: 'com.labre.uml.validation.communication-path-endpoints.suggestion',
1653
+ suggestionFallback: 'A communication path joins two machines — nodes, devices or execution environments — so that they can exchange messages. Draw it between the two cubes, and let the deployment arrows say which software sits on each.',
1654
+ version: 1,
1655
+ provenance: {
1656
+ source: 'standard',
1657
+ reference: 'OMG UML 2.5.1 §19.4.3 — a CommunicationPath is an Association between two DeploymentTargets, through which they may exchange Signals and Messages',
1658
+ },
1659
+ backgroundRole: UML_ROLE.diagram,
1660
+ endpoints: {
1661
+ edgeRole: UML_ROLE['communication-path'],
1662
+ allowed: UML_COMMUNICATION_PATH_MATRIX,
1663
+ },
1664
+ };
1665
+ /**
1666
+ * **U14** — a plain connector between two UML artefacts.
1667
+ *
1668
+ * The gap every other rule in this file falls through, and C4's `untyped-link`
1669
+ * and BPMN's `B4` one framework over. The UML relationship tools stamp a role;
1670
+ * quick-connect and auto-complete do not, so releasing the canvas' own link
1671
+ * gesture between two boxes produces a connector carrying NO role — which says
1672
+ * nothing to any grammar, nothing to the degree counts and nothing to the
1673
+ * exporters. A diagram joined that way LOOKS connected, exports as if nobody had
1674
+ * joined anything, and reads on screen as a finished drawing.
1675
+ *
1676
+ * So the rule is not really about the connector: it is about the verdicts and the
1677
+ * exports that go quiet behind it.
1678
+ *
1679
+ * ## Why it declares a matrix it never judges anything against
1680
+ *
1681
+ * `flagNeutral` reads the rule's own ALPHABET to decide which role-less links it
1682
+ * may presume were meant as relationships, so {@link UML_ELEMENT_MATRIX} is how
1683
+ * this rule says "between two UML artefacts" — and, by saying only that, how it
1684
+ * stays silent about everything else. A plain connector onto the frame, onto a
1685
+ * subject, onto a compartment, onto a sticky note, onto a shape somebody dropped
1686
+ * to think with is an annotation and none of UML's business.
1687
+ *
1688
+ * Nothing else in the declaration fires: the matrix sanctions all thirty-six
1689
+ * ordered pairs, so off-matrix is structurally unreachable here and the actor →
1690
+ * actor sentence stays {@link actorActorAssociation}'s finding alone.
1691
+ * `forbidSelfLoop` is absent for the same reason. This rule raises exactly one
1692
+ * kind of finding.
1693
+ */
1694
+ const untypedEdge = {
1695
+ id: 'uml.untyped-edge',
1696
+ framework: 'uml',
1697
+ family: 'relation-endpoints',
1698
+ severity: 'audit',
1699
+ roles: UML_ROLES,
1700
+ // The rule's own words are never read: `flagNeutral` is the only verdict it can
1701
+ // reach, and it carries its own. Declared all the same, because the shape
1702
+ // requires them and a rule with no sentence is a rule nobody can review.
1703
+ messageKey: 'com.labre.uml.validation.untyped-edge',
1704
+ messageFallback: 'This link between two UML artefacts says nothing the model records.',
1705
+ suggestionKey: 'com.labre.uml.validation.untyped-edge.suggestion',
1706
+ suggestionFallback: 'Draw it again with one of the relationship tools. A plain connector is on the diagram and absent from the model, so nothing can be said about what it means, which way round it reads, or how to write it into a file.',
1707
+ version: 1,
1708
+ provenance: {
1709
+ source: 'labre-convention',
1710
+ reference: 'Labre convention — a role-less connector is a gesture of this canvas, not an artefact of the notation',
1711
+ },
1712
+ backgroundRole: UML_ROLE.diagram,
1713
+ endpoints: {
1714
+ // Any of the nine would do — the neutral pass reads no edge role — and the
1715
+ // association is the one a user reaching for a line most often means.
1716
+ edgeRole: UML_ROLE.association,
1717
+ allowed: UML_ELEMENT_MATRIX,
1718
+ flagNeutral: {
1719
+ messageKey: 'com.labre.uml.validation.untyped-edge.neutral',
1720
+ messageFallback: 'These two artefacts are joined by an untyped link.',
1721
+ suggestionKey: 'com.labre.uml.validation.untyped-edge.neutral.suggestion',
1722
+ suggestionFallback: 'The diagram shows a line and the model holds none — nothing associates, specialises or depends on anything here. Draw it again with an association, a generalization, a realization or a dependency so it can carry a direction and be exported.',
1723
+ },
1724
+ },
1725
+ };
1726
+ /* ── Degree: how many lines may reach one artefact? ─────────────────────── */
1727
+ /**
1728
+ * **U15** — a part claimed by two composites.
1729
+ *
1730
+ * §11.5.3, and one of the few genuinely arithmetic constraints the notation has:
1731
+ * composition is a whole/part relationship in which the part's lifetime is the
1732
+ * whole's, so an object may be a part of at most one composite at a time. Two
1733
+ * filled diamonds pointing at the same box is a drawing that cannot be built.
1734
+ *
1735
+ * ## The PART is the target end
1736
+ *
1737
+ * `roles.ts` is explicit: for an aggregation and a composition the source is the
1738
+ * WHOLE — the end that carries the diamond, which `actions.ts` arms as the
1739
+ * connector's front endpoint. So the part is where the line ARRIVES, and the
1740
+ * bound is `maxIn: 1`.
1741
+ *
1742
+ * Aggregation is deliberately not counted: §11.5.4's shared aggregation has no
1743
+ * such constraint — a part may be shared by many wholes, which is the whole
1744
+ * difference between the hollow diamond and the filled one — and `uml:composition`
1745
+ * is a role of its own precisely so a rule can say one and not the other.
1746
+ *
1747
+ * ## Written on the classifiers, like {@link elementOutsideFrame}
1748
+ *
1749
+ * `edge-degree` names one subject role. A composition pointing at an instance or
1750
+ * a package is a stranger drawing than this rule's, and one
1751
+ * {@link dependencyOnObject}'s neighbourhood already covers in spirit; the part
1752
+ * of a composite is a classifier in every drawing anybody makes.
1753
+ */
1754
+ const compositionSingleOwner = {
1755
+ id: 'uml.composition-single-owner',
1756
+ framework: 'uml',
1757
+ family: 'edge-degree',
1758
+ severity: 'audit',
1759
+ appliesTo: UML_ROLE.classifier,
1760
+ roles: UML_ROLES,
1761
+ messageKey: 'com.labre.uml.validation.composition-single-owner',
1762
+ messageFallback: 'Two compositions claim this part.',
1763
+ suggestionKey: 'com.labre.uml.validation.composition-single-owner.suggestion',
1764
+ suggestionFallback: 'A composite owns its parts’ lifetime, so a part belongs to one whole at a time. Keep the composition that really owns it and make the other an aggregation — the hollow diamond, which is exactly the "shared part" the notation has.',
1765
+ version: 1,
1766
+ provenance: {
1767
+ source: 'standard',
1768
+ reference: 'OMG UML 2.5.1 §11.5.3 — a composite object is responsible for the existence of its parts; an object may be part of at most one composite at a time',
1769
+ },
1770
+ backgroundRole: UML_ROLE.diagram,
1771
+ degree: {
1772
+ edgeRole: UML_ROLE.composition,
1773
+ // The PART is where the diamond's line ARRIVES — see the header.
1774
+ maxIn: 1,
1775
+ },
1776
+ };
1777
+ /**
1778
+ * **U16** — a use case no actor is associated with.
1779
+ *
1780
+ * Not a constraint of the specification and it says so: §18.1 is perfectly happy
1781
+ * with a use case nobody has drawn an actor for, and there are real diagrams —
1782
+ * an included behaviour, a use case reached only through an extend — where that
1783
+ * is the right drawing.
1784
+ *
1785
+ * It is a USAGE remark, and the most useful one a finished use case diagram can
1786
+ * be given: a use case is a behaviour the system performs FOR somebody, so an
1787
+ * ellipse with no actor on either end is usually a use case whose actor has not
1788
+ * been drawn yet, or a system function that is not a use case at all.
1789
+ *
1790
+ * `eitherMin: 1` — at least one association on ONE side. The four ordinary bounds
1791
+ * cannot say it: `minIn: 1` indicts every use case an actor associates FROM,
1792
+ * `minOut: 1` every one drawn the other way round, and both together indict a
1793
+ * conformant diagram end to end. The direction of an association is not a fact
1794
+ * about the model (`roles.ts`: an association is UNDIRECTED), so the only honest
1795
+ * bound is the disjunctive one.
1796
+ *
1797
+ * `audit` at BOTH levels — `uml.strict` leaves it here — because the finding is
1798
+ * "this diagram is not finished", and not-finished is what a panel is for.
1799
+ */
1800
+ const useCaseNoActor = {
1801
+ id: 'uml.use-case-no-actor',
1802
+ framework: 'uml',
1803
+ family: 'edge-degree',
1804
+ severity: 'audit',
1805
+ appliesTo: UML_ROLE['use-case'],
1806
+ roles: UML_ROLES,
1807
+ messageKey: 'com.labre.uml.validation.use-case-no-actor',
1808
+ messageFallback: 'No actor is associated with this use case.',
1809
+ suggestionKey: 'com.labre.uml.validation.use-case-no-actor.suggestion',
1810
+ suggestionFallback: 'A use case is something the system does for somebody. Draw the association to the actor it serves — or, if it is only ever reached from another use case, leave it: this is a remark, not a rule.',
1811
+ version: 1,
1812
+ provenance: {
1813
+ source: 'labre-convention',
1814
+ reference: 'Labre convention — UML states no such constraint; a use case with no actor is usually a diagram somebody has not finished',
1815
+ },
1816
+ backgroundRole: UML_ROLE.diagram,
1817
+ degree: {
1818
+ edgeRole: UML_ROLE.association,
1819
+ // One association on EITHER side — see the header on why no conjunction of
1820
+ // the per-direction bounds expresses this.
1821
+ eitherMin: 1,
1822
+ },
1823
+ };
1824
+ /* ── Attachment: is the glyph still on the box it was drawn on? ──────────── */
1825
+ /**
1826
+ * **U43** — a port that has drifted into the middle of its component.
1827
+ *
1828
+ * The only `border-proximity` rule in the library (ADR 0024), and the one this
1829
+ * pack recorded for a whole phase as a question the engine could not be asked.
1830
+ * §11.3.4 draws a Port as "a small square symbol… placed ON the boundary of the
1831
+ * rectangle" of the EncapsulatedClassifier that owns it, and the placement IS
1832
+ * the statement: a port on the edge says "traffic crosses here", the same square
1833
+ * a hundred units inside says nothing the notation has a reading for. It is not
1834
+ * decoration and it is not a matter of taste — `model.ts` resolves a port's
1835
+ * owner from exactly this geometry, so a port dragged off the border is also a
1836
+ * port the XMI and the PlantUML writers will file under nothing.
1837
+ *
1838
+ * ## The tolerance, and why it is a number of units
1839
+ *
1840
+ * {@link UML_PORT_BORDER_TOLERANCE} — the glyph's own 16-unit side, derived from
1841
+ * `UML_NODE_BOX.port` rather than restated. `consts.ts` works the three drawings
1842
+ * through: half in and half out (§11.3.4's own picture) is distance 0, tangent
1843
+ * inside is 8, and a square pushed a full glyph clear of the edge is 24 and the
1844
+ * finding falls. Absolute and not a ratio of the component, because what makes
1845
+ * "on the border" legible is the square against the line and not the size of the
1846
+ * box behind it.
1847
+ *
1848
+ * ## What it stays silent about
1849
+ *
1850
+ * A port touching NO component at all — a square dropped on blank canvas, or
1851
+ * parked beside the sheet while the diagram is being rearranged. That is the
1852
+ * family's own gate (`border-proximity` judges a glyph the author has already
1853
+ * put on a box), and it is the right silence: a port belonging to nothing is
1854
+ * somebody drawing, not somebody wrong (PRD principle 8). The same square on the
1855
+ * border of a CUBE is silence too — the rule's carrier is `uml:component`, and
1856
+ * `model.ts` already says a port drawn on a deployment node is a drawing this
1857
+ * pack keeps and writes nowhere.
1858
+ *
1859
+ * A port ON the border but outside its component (the half of the square that
1860
+ * sticks out being the larger half) is silence as well, at any drawing a hand
1861
+ * produces: the centre is at most half a glyph out, which is inside the
1862
+ * tolerance by construction.
1863
+ */
1864
+ const portOnBorder = {
1865
+ id: 'uml.port-on-border',
1866
+ framework: 'uml',
1867
+ family: 'border-proximity',
1868
+ severity: 'audit',
1869
+ appliesTo: UML_ROLE.port,
1870
+ roles: UML_ROLES,
1871
+ messageKey: 'com.labre.uml.validation.port-on-border',
1872
+ messageFallback: 'This port sits inside its component, not on its border.',
1873
+ suggestionKey: 'com.labre.uml.validation.port-on-border.suggestion',
1874
+ suggestionFallback: 'A port is where something crosses the boundary of the component, so UML draws its square ON that boundary — straddling the edge, or just touching it from inside. Drag it back to the edge: the export reads which component owns a port from where the square sits, and one in the middle of the box belongs to nothing.',
1875
+ version: 1,
1876
+ provenance: {
1877
+ source: 'standard',
1878
+ reference: 'OMG UML 2.5.1 §11.3.4 — "A Port is shown as a small square symbol. The square symbol is placed on the boundary of the rectangle symbol for the owning EncapsulatedClassifier"',
1879
+ },
1880
+ backgroundRole: UML_ROLE.diagram,
1881
+ borderProximity: {
1882
+ carrierRole: UML_ROLE.component,
1883
+ tolerance: UML_PORT_BORDER_TOLERANCE,
1884
+ },
1885
+ };
1886
+ /* ── Behaviour: the alphabets and the grammars of the two flow sheets ────── */
1887
+ /**
1888
+ * The ACTIVITY vertices — what a flow may be drawn between on an `act` sheet.
1889
+ *
1890
+ * `uml:control-node` stands for its five children by declaration (`roles.ts`),
1891
+ * so the initial disc, the two finals, the decision diamond and the fork bar
1892
+ * arrive through `roleIsA` rather than as five entries. The object node is
1893
+ * deliberately NOT here: §15.7.9.4 keeps it off a control flow, which is the
1894
+ * whole content of {@link UML_CONTROL_FLOW_MATRIX}.
1895
+ */
1896
+ const UML_ACTIVITY_VERTEX_ROLES = [
1897
+ UML_ROLE.action,
1898
+ UML_ROLE['control-node'],
1899
+ UML_ROLE['send-signal'],
1900
+ UML_ROLE['accept-event'],
1901
+ UML_ROLE['time-event'],
1902
+ ];
1903
+ /**
1904
+ * The STATE MACHINE vertices — §14.5.12's "source Vertex" and "target Vertex".
1905
+ *
1906
+ * `uml:pseudostate` stands for the choice, the junction, the two histories, the
1907
+ * two connection points and the terminate cross. `uml:initial` is named beside
1908
+ * it rather than through it, because `roles.ts` files the initial disc under
1909
+ * `uml:control-node` — the same glyph means the same thing on both sheets, and
1910
+ * the vocabulary says so once instead of declaring it twice. `uml:fork` is here
1911
+ * for the same cross-family reason: §14.2.4 draws the fork and join bars in a
1912
+ * state machine and §15.3.4 draws them in an activity, and one role carries both.
1913
+ */
1914
+ const UML_STATE_VERTEX_ROLES = [
1915
+ UML_ROLE.state,
1916
+ UML_ROLE['final-state'],
1917
+ UML_ROLE.pseudostate,
1918
+ UML_ROLE.initial,
1919
+ UML_ROLE.fork,
1920
+ ];
1921
+ /**
1922
+ * ALPHABET entries for the state-machine vocabulary, carried by the two ACTIVITY
1923
+ * grammars.
1924
+ *
1925
+ * The device the file header explains, applied across the two behaviour sheets:
1926
+ * these are true sentences of §14.5.12 carrying the `uml:transition` role, so
1927
+ * `inMatrix` — which matches a triplet's edge with `roleIsA` — can never let one
1928
+ * sanction a control flow or an object flow. Their whole effect is to put
1929
+ * `uml:state`, `uml:final-state` and `uml:pseudostate` into the alphabet the two
1930
+ * activity rules speak, which is what makes "a control flow dragged onto a
1931
+ * state" a finding rather than silence.
1932
+ */
1933
+ const UML_STATE_ALPHABET = [
1934
+ {
1935
+ source: UML_ROLE.state,
1936
+ edge: UML_ROLE.transition,
1937
+ target: UML_ROLE.state,
1938
+ },
1939
+ {
1940
+ source: UML_ROLE.state,
1941
+ edge: UML_ROLE.transition,
1942
+ target: UML_ROLE['final-state'],
1943
+ },
1944
+ {
1945
+ source: UML_ROLE.pseudostate,
1946
+ edge: UML_ROLE.transition,
1947
+ target: UML_ROLE.state,
1948
+ },
1949
+ ];
1950
+ /**
1951
+ * ALPHABET entries for the activity vocabulary, carried by the TRANSITION
1952
+ * grammar — the mirror of {@link UML_STATE_ALPHABET}.
1953
+ *
1954
+ * Three foreign sentences putting `uml:action`, `uml:control-node` and
1955
+ * `uml:object-node` into the alphabet {@link transitionEndpoints} speaks. The
1956
+ * control-node entry is the one that earns its place twice over: it is what makes
1957
+ * a transition drawn onto a DECISION diamond a finding, because a decision is
1958
+ * §15.3.4's branch and `uml:choice` is §14.2.4's — two glyphs that look alike,
1959
+ * mean the same thing on two different sheets, and are the commonest confusion
1960
+ * this pair of diagrams produces.
1961
+ */
1962
+ const UML_ACTIVITY_ALPHABET = [
1963
+ {
1964
+ source: UML_ROLE.action,
1965
+ edge: UML_ROLE['control-flow'],
1966
+ target: UML_ROLE['control-node'],
1967
+ },
1968
+ {
1969
+ source: UML_ROLE['object-node'],
1970
+ edge: UML_ROLE['object-flow'],
1971
+ target: UML_ROLE.action,
1972
+ },
1973
+ // …and the class-side vocabulary, so a behaviour edge dragged onto a class is
1974
+ // judged rather than ignored. `uml:classifier` reaches all three of them.
1975
+ {
1976
+ source: UML_ROLE.classifier,
1977
+ edge: UML_ROLE.association,
1978
+ target: UML_ROLE.classifier,
1979
+ },
1980
+ ];
1981
+ /**
1982
+ * What a CONTROL FLOW may run between: the activity vertices, and never an
1983
+ * object node.
1984
+ *
1985
+ * §15.7.9.4 is one of the few constraints in Clause 15 that OCL can state in a
1986
+ * line — "ControlFlows may not have ObjectNodes at either end, except for
1987
+ * ObjectNodes with control type" — and the exception is the reason the rule that
1988
+ * holds this table stays a remark rather than a defect: `isControlType` is a
1989
+ * property of the metamodel with no notation at all (§15.4.4 draws the same
1990
+ * rectangle either way), so a canvas cannot say which of the two an author meant.
1991
+ * The table therefore reports the drawing the specification's own default
1992
+ * forbids, and the rule's suggestion names the exception.
1993
+ *
1994
+ * Twenty-five sentences over five roles, plus the four alphabet entries: the
1995
+ * grammar judges WHICH vertices, never how many edges reach one. The counts are
1996
+ * {@link initialNoIncomingFlow}, {@link activityFinalNoOutgoing},
1997
+ * {@link flowFinalNoOutgoing} and {@link decisionIncomingCount}, and keeping the
1998
+ * two apart is what stops one wrong arrow being reported twice.
1999
+ */
2000
+ export const UML_CONTROL_FLOW_MATRIX = [
2001
+ ...UML_ACTIVITY_VERTEX_ROLES.flatMap(source => UML_ACTIVITY_VERTEX_ROLES.map(target => ({
2002
+ source,
2003
+ edge: UML_ROLE['control-flow'],
2004
+ target,
2005
+ }))),
2006
+ // The ALPHABET entries — the state-machine vocabulary, and the class-side one.
2007
+ ...UML_STATE_ALPHABET,
2008
+ {
2009
+ source: UML_ROLE.classifier,
2010
+ edge: UML_ROLE.association,
2011
+ target: UML_ROLE.classifier,
2012
+ },
2013
+ // …and the object node, named by a sentence of the OTHER flow so that it is in
2014
+ // the alphabet and outside the grammar. Without it a control flow drawn onto a
2015
+ // rectangle of data would be unjudged, which is the one drawing §15.7.9.4
2016
+ // actually names.
2017
+ {
2018
+ source: UML_ROLE['object-node'],
2019
+ edge: UML_ROLE['object-flow'],
2020
+ target: UML_ROLE.action,
2021
+ },
2022
+ ];
2023
+ /**
2024
+ * What an OBJECT FLOW may run between: something, and an object node.
2025
+ *
2026
+ * §15.7.11.4's `same_upper_bounds` states the shape of it — "ObjectNodes
2027
+ * connected by an ObjectFlow, with optionally intervening ControlNodes" — so an
2028
+ * object flow is a line between DATA, routed through control nodes where it
2029
+ * forks or merges. One end being an object node is the loosest honest reading of
2030
+ * that, and it is the one drawn here: the strict reading (both ends) would indict
2031
+ * the object node that feeds a decision, which §15.7.11.4 explicitly allows.
2032
+ *
2033
+ * ## Why the rule that holds this stays a remark at every level
2034
+ *
2035
+ * §15.4.4 draws an action's PINS as small squares on its border and says in as
2036
+ * many words that they may be elided, in which case the object flow is drawn
2037
+ * from one action straight to the other. A pin IS an ObjectNode, so that drawing
2038
+ * is a legal elision of a conformant model and not a defect — and this canvas
2039
+ * has no pin. So the finding is "there is no data node on this flow", which is a
2040
+ * reading worth offering and never a conformance defect, and `uml.strict` leaves
2041
+ * it at `audit` for that reason alone (`profiles.ts`).
2042
+ */
2043
+ const UML_OBJECT_FLOW_ENDS = [
2044
+ ...UML_ACTIVITY_VERTEX_ROLES,
2045
+ UML_ROLE['object-node'],
2046
+ ];
2047
+ export const UML_OBJECT_FLOW_MATRIX = [
2048
+ ...UML_OBJECT_FLOW_ENDS.map(target => ({
2049
+ source: UML_ROLE['object-node'],
2050
+ edge: UML_ROLE['object-flow'],
2051
+ target,
2052
+ })),
2053
+ ...UML_OBJECT_FLOW_ENDS.filter(source => source !== UML_ROLE['object-node']).map(source => ({
2054
+ source,
2055
+ edge: UML_ROLE['object-flow'],
2056
+ target: UML_ROLE['object-node'],
2057
+ })),
2058
+ // The ALPHABET entries — the state-machine vocabulary, and the class-side one.
2059
+ ...UML_STATE_ALPHABET,
2060
+ {
2061
+ source: UML_ROLE.classifier,
2062
+ edge: UML_ROLE.association,
2063
+ target: UML_ROLE.classifier,
2064
+ },
2065
+ ];
2066
+ /**
2067
+ * What a TRANSITION may run between: the vertices of a state machine, and
2068
+ * nothing else.
2069
+ *
2070
+ * §14.5.12 in one sentence — "A Transition is a single directed arc originating
2071
+ * from a single source Vertex and terminating on a single target Vertex" — and a
2072
+ * Vertex is a State, a FinalState or a Pseudostate (§14.5.14). So the table is
2073
+ * the full square over {@link UML_STATE_VERTEX_ROLES}: twenty-five sentences,
2074
+ * judging WHICH vertices and not how many arcs reach one.
2075
+ *
2076
+ * ## The square is deliberate, and it is what stops the double report
2077
+ *
2078
+ * A transition INTO an initial disc and a transition OUT OF a final state are
2079
+ * both wrong, and both are already said — by {@link initialNoIncomingTransition}
2080
+ * and {@link finalStateNoOutgoing}, each of which names the side the author has
2081
+ * to act on. Removing those two sentences from this table would make the same
2082
+ * arrow off-matrix as well, and the user would get two brackets and two
2083
+ * suggestions for one drag to fix. The DEGREE rules own the counts; this table
2084
+ * owns the vocabulary.
2085
+ */
2086
+ export const UML_TRANSITION_MATRIX = [
2087
+ ...UML_STATE_VERTEX_ROLES.flatMap(source => UML_STATE_VERTEX_ROLES.map(target => ({
2088
+ source,
2089
+ edge: UML_ROLE.transition,
2090
+ target,
2091
+ }))),
2092
+ // The ALPHABET entries — the activity vocabulary, and the class-side one.
2093
+ ...UML_ACTIVITY_ALPHABET,
2094
+ ];
2095
+ /* ── Behaviour: membership in a lane and in a composite state ───────────── */
2096
+ /**
2097
+ * **U20** — an action drawn outside every partition, while partitions exist.
2098
+ *
2099
+ * §15.6.4 draws an ActivityPartition as a swimlane: a band across the sheet with
2100
+ * a name at its head, holding the actions whoever the band names is responsible
2101
+ * for. The lanes are what the diagram is FOR once they are drawn — a reader
2102
+ * follows the flow across them to see where responsibility changes hands — and
2103
+ * an action floating between two of them belongs to nobody.
2104
+ *
2105
+ * ## The family is `element-in-background`, and the silence is the whole point
2106
+ *
2107
+ * {@link useCaseOutsideSubject}'s arrangement one notation over: a partition on
2108
+ * the board is the frame, an action contained by NONE of them is the finding,
2109
+ * and a board with no partition at all is total silence. Most activity diagrams
2110
+ * never draw a lane, and those are complete diagrams — the rule speaks only once
2111
+ * an author has said that responsibility is part of what this sheet shows.
2112
+ *
2113
+ * ## Written on the ACTION, and the limit that follows
2114
+ *
2115
+ * `element-in-background` names one subject role. The control nodes, the object
2116
+ * nodes and the three signal artefacts drawn between the lanes raise nothing —
2117
+ * and that is the right narrowing rather than a shortfall: §15.6.3 attributes
2118
+ * BEHAVIOUR to a partition, a decision diamond routing a flow between two lanes
2119
+ * is routinely drawn on the boundary on purpose, and a fork bar spanning three
2120
+ * lanes is §15.6.4's own figure. The action is the artefact a lane is about.
2121
+ */
2122
+ const nodeInPartition = {
2123
+ id: 'uml.node-in-partition',
2124
+ framework: 'uml',
2125
+ family: 'element-in-background',
2126
+ severity: 'audit',
2127
+ appliesTo: UML_ROLE.action,
2128
+ roles: UML_ROLES,
2129
+ messageKey: 'com.labre.uml.validation.node-in-partition',
2130
+ messageFallback: 'This action sits outside every partition.',
2131
+ suggestionKey: 'com.labre.uml.validation.node-in-partition.suggestion',
2132
+ suggestionFallback: 'The swimlanes say who is responsible for each step. Move the action into the lane that owns it, or stretch the lane round it — a diagram that draws lanes is read by following the flow across them.',
2133
+ version: 1,
2134
+ provenance: {
2135
+ source: 'labre-convention',
2136
+ reference: 'Labre convention — OMG UML 2.5.1 §15.6.4 draws the swimlane; requiring an action to be inside one once lanes exist is membership on this canvas',
2137
+ },
2138
+ backgroundRole: UML_ROLE.partition,
2139
+ };
2140
+ /**
2141
+ * **U21 / U22** — a history pseudostate drawn outside every composite state.
2142
+ *
2143
+ * §14.2.3: a shallow history "represents the most recent active substate of its
2144
+ * containing Region" and a deep history the most recent configuration of the
2145
+ * whole sub-machine. Both are statements ABOUT a region, and a region on this
2146
+ * canvas is the `umlRegion` box the author drew (`roles.ts` records why the
2147
+ * composite state IS the frame). An `H` circle floating on the top-level sheet
2148
+ * therefore remembers the history of nothing.
2149
+ *
2150
+ * ## TWO rules for one sentence, and the reason is the family
2151
+ *
2152
+ * `element-in-background` names ONE subject role, and the vocabulary has no
2153
+ * parent meaning "a history": `uml:shallow-history` and `uml:deep-history` are
2154
+ * flat siblings under `uml:pseudostate`, which also covers the choice, the
2155
+ * junction, the two connection points and the terminate cross — none of which is
2156
+ * confined to a region. A rule written on the parent would indict every choice
2157
+ * diamond on a flat state machine, which is most of them.
2158
+ *
2159
+ * So the pack ships the sentence twice, and nothing is reported twice: an element
2160
+ * carries one role, so exactly one of the two can ever be about it. The same
2161
+ * split {@link initialNoIncomingFlow} and {@link initialNoIncomingTransition}
2162
+ * make for the other reason a family names one thing at a time.
2163
+ *
2164
+ * The provenance is `recommendation` rather than `standard` because the step from
2165
+ * §14.2.3's OWNERSHIP to this canvas's CONTAINMENT is ours: the metamodel says
2166
+ * the Pseudostate belongs to a Region, and reading a drawn box as that ownership
2167
+ * is the reading `roles.ts` chose, not a clause.
2168
+ */
2169
+ const shallowHistoryOutsideRegion = {
2170
+ id: 'uml.shallow-history-outside-region',
2171
+ framework: 'uml',
2172
+ family: 'element-in-background',
2173
+ severity: 'audit',
2174
+ appliesTo: UML_ROLE['shallow-history'],
2175
+ roles: UML_ROLES,
2176
+ messageKey: 'com.labre.uml.validation.shallow-history-outside-region',
2177
+ messageFallback: 'This history is drawn outside every composite state.',
2178
+ suggestionKey: 'com.labre.uml.validation.shallow-history-outside-region.suggestion',
2179
+ suggestionFallback: 'A history remembers where a composite state was when it was last left, so it has to be drawn inside the one it remembers. Move the H into the composite state, or draw the composite state round it.',
2180
+ version: 1,
2181
+ provenance: {
2182
+ source: 'recommendation',
2183
+ reference: 'OMG UML 2.5.1 §14.2.3 — a shallowHistory Pseudostate represents the most recent active substate of its containing Region',
2184
+ },
2185
+ backgroundRole: UML_ROLE.region,
2186
+ };
2187
+ const deepHistoryOutsideRegion = {
2188
+ id: 'uml.deep-history-outside-region',
2189
+ framework: 'uml',
2190
+ family: 'element-in-background',
2191
+ severity: 'audit',
2192
+ appliesTo: UML_ROLE['deep-history'],
2193
+ roles: UML_ROLES,
2194
+ messageKey: 'com.labre.uml.validation.deep-history-outside-region',
2195
+ messageFallback: 'This deep history is drawn outside every composite state.',
2196
+ suggestionKey: 'com.labre.uml.validation.deep-history-outside-region.suggestion',
2197
+ suggestionFallback: 'A deep history restores the whole configuration a composite state was last in, so it has to be drawn inside the one it restores. Move the H* into the composite state, or draw the composite state round it.',
2198
+ version: 1,
2199
+ provenance: {
2200
+ source: 'recommendation',
2201
+ reference: 'OMG UML 2.5.1 §14.2.3 — a deepHistory Pseudostate represents the most recent active state configuration of its containing Region',
2202
+ },
2203
+ backgroundRole: UML_ROLE.region,
2204
+ };
2205
+ /* ── Behaviour: how many beginnings, and how many lines reach one ───────── */
2206
+ /**
2207
+ * **U23** — a second filled disc on one sheet.
2208
+ *
2209
+ * The one rule in this pack whose authority differs between the two sheets it
2210
+ * speaks on, and it says so rather than picking the flattering half:
2211
+ *
2212
+ * - on a **state machine**, §14.5.6.4 is normative and arithmetic — "A Region
2213
+ * can have at most one initial Vertex";
2214
+ * - on an **activity**, §15.3.3 says the opposite in as many words — "An
2215
+ * Activity may have more than one InitialNode", each one starting a concurrent
2216
+ * flow.
2217
+ *
2218
+ * So the provenance is `recommendation`: a second disc on an activity sheet is a
2219
+ * drawing the specification permits, and most of the ones anybody draws are a
2220
+ * disc the author forgot to delete rather than a deliberate concurrent start. The
2221
+ * rule offers the reading and never claims UML forbids it, which is the whole
2222
+ * point of the field.
2223
+ *
2224
+ * ## Counted per DIAGRAM, and the refinement that is not phase 2's
2225
+ *
2226
+ * §14.5.6.4's bound is per REGION, and a state machine with three composite
2227
+ * states legitimately holds four initial discs — one for the top level and one
2228
+ * inside each. `role-count` counts per instance of ONE frame, named by
2229
+ * `backgroundRole`, and this rule names the diagram: the per-region reading needs
2230
+ * the same bound counted against `uml:region` instances as well, which is a
2231
+ * second rule of the same family and a PHASE 3 refinement. Until it lands a
2232
+ * composite state with its own beginning is a finding here, which is the wrong
2233
+ * verdict on a right drawing — and the reason this rule is `audit` in the default
2234
+ * profile like everything else, rather than something a user has to argue with.
2235
+ *
2236
+ * The finding lands on the FRAME, because no disc is at fault: there are simply
2237
+ * two, and `role-count` raises on the instance so an arbitration made on one
2238
+ * sheet covers that sheet alone.
2239
+ */
2240
+ const initialSingle = {
2241
+ id: 'uml.initial-single',
2242
+ framework: 'uml',
2243
+ family: 'role-count',
2244
+ severity: 'audit',
2245
+ // No `appliesTo`: `role-count` names its subject in `roleCount.subject`, and
2246
+ // the finding is about the frame rather than about any one element.
2247
+ roles: UML_ROLES,
2248
+ messageKey: 'com.labre.uml.validation.initial-single',
2249
+ messageFallback: 'This sheet draws more than one beginning.',
2250
+ suggestionKey: 'com.labre.uml.validation.initial-single.suggestion',
2251
+ suggestionFallback: 'A state machine region has at most one initial vertex, and a reader looks for one place the behaviour starts. Keep the disc the flow really begins at and delete the others — unless the sheet is an activity deliberately starting several concurrent flows, which UML allows.',
2252
+ version: 1,
2253
+ provenance: {
2254
+ source: 'recommendation',
2255
+ reference: 'OMG UML 2.5.1 §14.5.6.4 — "A Region can have at most one initial Vertex"; §15.3.3 allows an Activity more than one InitialNode, so on an activity sheet this is a reading rather than a clause',
2256
+ },
2257
+ backgroundRole: UML_ROLE.diagram,
2258
+ roleCount: {
2259
+ subject: UML_ROLE.initial,
2260
+ max: 1,
2261
+ },
2262
+ };
2263
+ /**
2264
+ * **U24** — a control flow pointing AT the disc the flow begins at.
2265
+ *
2266
+ * §15.3.3, verbatim: "An InitialNode shall not have any incoming ActivityEdges."
2267
+ * An arrow into the black disc is almost always an arrow drawn the wrong way
2268
+ * round, and the fix is one drag.
2269
+ *
2270
+ * ## Two rules, one sentence, and the family is the reason
2271
+ *
2272
+ * `edge-degree` counts ONE edge role. The same disc is §14.2.4's initial
2273
+ * Pseudostate on a state machine, where the arrow that must not exist is a
2274
+ * TRANSITION and not a control flow, so the requirement is two counts over two
2275
+ * vocabularies. They never double-report — an edge carries one role, and the two
2276
+ * roles are flat siblings — and they carry different authority, which is the
2277
+ * second reason to keep them apart: §15.3.3 states the activity half outright,
2278
+ * while the state machine half is a reading of §14.2.3 (see
2279
+ * {@link initialNoIncomingTransition}).
2280
+ *
2281
+ * ## The object flow is not counted, and that is deliberate
2282
+ *
2283
+ * §15.3.3's next sentence — "The outgoing ActivityEdges of an InitialNode must
2284
+ * all be ControlFlows" — would make an object flow at either end of a disc a
2285
+ * finding too. It is not asked here: a third rule would be a third bracket on the
2286
+ * same glyph, and an object flow drawn onto a control node is already
2287
+ * {@link objectFlowEndpoints}' business through its own matrix.
2288
+ */
2289
+ const initialNoIncomingFlow = {
2290
+ id: 'uml.initial-no-incoming-flow',
2291
+ framework: 'uml',
2292
+ family: 'edge-degree',
2293
+ severity: 'audit',
2294
+ appliesTo: UML_ROLE.initial,
2295
+ roles: UML_ROLES,
2296
+ messageKey: 'com.labre.uml.validation.initial-no-incoming-flow',
2297
+ messageFallback: 'A control flow points at the node this activity begins at.',
2298
+ suggestionKey: 'com.labre.uml.validation.initial-no-incoming-flow.suggestion',
2299
+ suggestionFallback: 'The filled disc is where the flow starts, so nothing arrives at it. Turn the arrow round, or point it at the first action instead — and if the flow really does come back here, it comes back to that action.',
2300
+ version: 1,
2301
+ provenance: {
2302
+ source: 'standard',
2303
+ reference: 'OMG UML 2.5.1 §15.3.3 — "An InitialNode shall not have any incoming ActivityEdges"',
2304
+ },
2305
+ backgroundRole: UML_ROLE.diagram,
2306
+ degree: {
2307
+ edgeRole: UML_ROLE['control-flow'],
2308
+ maxIn: 0,
2309
+ },
2310
+ };
2311
+ /**
2312
+ * **U25** — a transition pointing AT the disc a state machine begins at.
2313
+ *
2314
+ * {@link initialNoIncomingFlow}'s twin over the state machine vocabulary. §14.2.3
2315
+ * describes the initial Pseudostate as "a starting point for a Region; that is,
2316
+ * it is the point from which execution begins", and §14.5.6.4 gives it at most
2317
+ * one OUTGOING transition — the clause constrains what leaves and says nothing
2318
+ * about what arrives, because a Vertex nothing can reach is not a case the
2319
+ * metamodel needed to exclude.
2320
+ *
2321
+ * So the provenance is `recommendation` where its activity twin is `standard`,
2322
+ * and the two rules are a small demonstration of why the field exists: the same
2323
+ * drawing, the same fix, and one of them is a conformance defect while the other
2324
+ * is the tool being helpful.
2325
+ */
2326
+ const initialNoIncomingTransition = {
2327
+ id: 'uml.initial-no-incoming-transition',
2328
+ framework: 'uml',
2329
+ family: 'edge-degree',
2330
+ severity: 'audit',
2331
+ appliesTo: UML_ROLE.initial,
2332
+ roles: UML_ROLES,
2333
+ messageKey: 'com.labre.uml.validation.initial-no-incoming-transition',
2334
+ messageFallback: 'A transition points at the node this state machine begins at.',
2335
+ suggestionKey: 'com.labre.uml.validation.initial-no-incoming-transition.suggestion',
2336
+ suggestionFallback: 'The filled disc is where the machine starts, so nothing transitions into it. Point the arrow at the state the machine should return to instead.',
2337
+ version: 1,
2338
+ provenance: {
2339
+ source: 'recommendation',
2340
+ reference: 'OMG UML 2.5.1 §14.2.3 — an initial Pseudostate is the point from which execution begins; §14.5.6.4 constrains what leaves it and not what arrives',
2341
+ },
2342
+ backgroundRole: UML_ROLE.diagram,
2343
+ degree: {
2344
+ edgeRole: UML_ROLE.transition,
2345
+ maxIn: 0,
2346
+ },
2347
+ };
2348
+ /**
2349
+ * **U26 / U27** — a flow leaving a node that ends one.
2350
+ *
2351
+ * §15.7.19.4, verbatim and for both of them: "A FinalNode has no outgoing
2352
+ * ActivityEdges." An activity final stops every flow in the activity and a flow
2353
+ * final destroys the one token that reached it — in neither case is there
2354
+ * anything left to leave.
2355
+ *
2356
+ * TWO rules again, and this time because the vocabulary is flat where the
2357
+ * metamodel is not: §15.7.19 makes ActivityFinalNode and FlowFinalNode two
2358
+ * FinalNodes, and `roles.ts` files both directly under `uml:control-node`
2359
+ * alongside the initial disc, the decision and the fork — so there is no
2360
+ * ancestor meaning "a final" that does not also reach the four glyphs a flow
2361
+ * legitimately leaves. Naming the two is the only reading that is right about
2362
+ * both.
2363
+ *
2364
+ * Their sentences differ, and deliberately: what a reader has to understand is
2365
+ * different. An arrow off an activity final says the author thinks the activity
2366
+ * continues; an arrow off a flow final says they think one branch does.
2367
+ */
2368
+ const activityFinalNoOutgoing = {
2369
+ id: 'uml.activity-final-no-outgoing',
2370
+ framework: 'uml',
2371
+ family: 'edge-degree',
2372
+ severity: 'audit',
2373
+ appliesTo: UML_ROLE['activity-final'],
2374
+ roles: UML_ROLES,
2375
+ messageKey: 'com.labre.uml.validation.activity-final-no-outgoing',
2376
+ messageFallback: 'A control flow leaves the node this activity ends at.',
2377
+ suggestionKey: 'com.labre.uml.validation.activity-final-no-outgoing.suggestion',
2378
+ suggestionFallback: 'The bullseye stops every flow in the activity, so nothing continues past it. Draw the arrow from the last action instead — or, if only this branch ends here, use the flow final (the circle with the cross).',
2379
+ version: 1,
2380
+ provenance: {
2381
+ source: 'standard',
2382
+ reference: 'OMG UML 2.5.1 §15.7.19.4 — "A FinalNode has no outgoing ActivityEdges"',
2383
+ },
2384
+ backgroundRole: UML_ROLE.diagram,
2385
+ degree: {
2386
+ edgeRole: UML_ROLE['control-flow'],
2387
+ maxOut: 0,
2388
+ },
2389
+ };
2390
+ const flowFinalNoOutgoing = {
2391
+ id: 'uml.flow-final-no-outgoing',
2392
+ framework: 'uml',
2393
+ family: 'edge-degree',
2394
+ severity: 'audit',
2395
+ appliesTo: UML_ROLE['flow-final'],
2396
+ roles: UML_ROLES,
2397
+ messageKey: 'com.labre.uml.validation.flow-final-no-outgoing',
2398
+ messageFallback: 'A control flow leaves the node this branch ends at.',
2399
+ suggestionKey: 'com.labre.uml.validation.flow-final-no-outgoing.suggestion',
2400
+ suggestionFallback: 'The circle with the cross destroys the token that reaches it, so this branch stops there. Draw the arrow from the action before it, or delete the flow final if the branch does carry on.',
2401
+ version: 1,
2402
+ provenance: {
2403
+ source: 'standard',
2404
+ reference: 'OMG UML 2.5.1 §15.7.19.4 — "A FinalNode has no outgoing ActivityEdges"',
2405
+ },
2406
+ backgroundRole: UML_ROLE.diagram,
2407
+ degree: {
2408
+ edgeRole: UML_ROLE['control-flow'],
2409
+ maxOut: 0,
2410
+ },
2411
+ };
2412
+ /**
2413
+ * **U28** — a transition leaving a final state.
2414
+ *
2415
+ * §14.5.11.4, verbatim: "A FinalState cannot have any outgoing Transitions." The
2416
+ * same shape as the two above, one notation over, and the same one-drag fix.
2417
+ */
2418
+ const finalStateNoOutgoing = {
2419
+ id: 'uml.final-state-no-outgoing',
2420
+ framework: 'uml',
2421
+ family: 'edge-degree',
2422
+ severity: 'audit',
2423
+ appliesTo: UML_ROLE['final-state'],
2424
+ roles: UML_ROLES,
2425
+ messageKey: 'com.labre.uml.validation.final-state-no-outgoing',
2426
+ messageFallback: 'A transition leaves this final state.',
2427
+ suggestionKey: 'com.labre.uml.validation.final-state-no-outgoing.suggestion',
2428
+ suggestionFallback: 'A final state is where the machine stops, so nothing transitions out of it. Draw the transition from the state before it — and if the machine really does carry on, this is not the end.',
2429
+ version: 1,
2430
+ provenance: {
2431
+ source: 'standard',
2432
+ reference: 'OMG UML 2.5.1 §14.5.11.4 — "A FinalState cannot have any outgoing Transitions"',
2433
+ },
2434
+ backgroundRole: UML_ROLE.diagram,
2435
+ degree: {
2436
+ edgeRole: UML_ROLE.transition,
2437
+ maxOut: 0,
2438
+ },
2439
+ };
2440
+ /**
2441
+ * **U29** — a decision diamond fed by three arrows.
2442
+ *
2443
+ * §15.7.11.4, and it is arithmetic: "A DecisionNode has one or two incoming
2444
+ * ActivityEdges and at least one outgoing ActivityEdge." TWO rather than one,
2445
+ * because the second incoming edge has a job — §15.3.4's `decisionInputFlow`
2446
+ * carries the value the guards are evaluated against — and a diamond with three
2447
+ * arrows into it is a merge somebody drew as a decision.
2448
+ *
2449
+ * ## `maxIn: 2`, and not the `maxIn: 1` a reader might expect
2450
+ *
2451
+ * Worth stating because the id would let you assume otherwise and because the
2452
+ * brief this rule was written from asked for one. The clause says two, the second
2453
+ * one is a real and named feature of the notation, and data that contradicts the
2454
+ * clause it cites is the one thing this pack cannot ship. The id names the
2455
+ * QUESTION — how many arrive — rather than the bound.
2456
+ *
2457
+ * ## The floor is not asked for
2458
+ *
2459
+ * "At least one incoming" would fire on every diamond for the whole of the time
2460
+ * between dropping it and joining it up, which is the definition of a rule that
2461
+ * argues with a croquis. The ceiling is the half that reports a mistake rather
2462
+ * than an unfinished drawing.
2463
+ */
2464
+ const decisionIncomingCount = {
2465
+ id: 'uml.decision-incoming-count',
2466
+ framework: 'uml',
2467
+ family: 'edge-degree',
2468
+ severity: 'audit',
2469
+ appliesTo: UML_ROLE.decision,
2470
+ roles: UML_ROLES,
2471
+ messageKey: 'com.labre.uml.validation.decision-incoming-count',
2472
+ messageFallback: 'More than two control flows arrive at this decision.',
2473
+ suggestionKey: 'com.labre.uml.validation.decision-incoming-count.suggestion',
2474
+ suggestionFallback: 'A decision takes the flow it branches and, at most, a second one carrying the value its guards test. If these arrows are branches coming back together, draw a merge — the same diamond, with the arrows leaving it joined into one.',
2475
+ version: 1,
2476
+ provenance: {
2477
+ source: 'standard',
2478
+ reference: 'OMG UML 2.5.1 §15.7.11.4 — "A DecisionNode has one or two incoming ActivityEdges and at least one outgoing ActivityEdge"',
2479
+ },
2480
+ backgroundRole: UML_ROLE.diagram,
2481
+ degree: {
2482
+ edgeRole: UML_ROLE['control-flow'],
2483
+ // TWO, which is what the clause says — see the header on why the id does not
2484
+ // name the bound.
2485
+ maxIn: 2,
2486
+ },
2487
+ };
2488
+ /* ── Behaviour: what each flow may run between ──────────────────────────── */
2489
+ /**
2490
+ * **U30** — a control flow with an object node, a state or a class at one end.
2491
+ *
2492
+ * §15.7.9.4's `object_nodes` constraint, and the alphabet entries that let this
2493
+ * rule reach the two confusions a mixed board actually produces: a control flow
2494
+ * dragged onto a STATE (the state machine sheet's vocabulary, one tool away in
2495
+ * the same menu) and a control flow dragged onto a CLASS.
2496
+ *
2497
+ * The object node is the clause's own case and the one with an exception: an
2498
+ * ObjectNode whose `isControlType` is true may sit on a control flow, and
2499
+ * §15.4.4 gives that property no notation at all, so a drawing cannot state it.
2500
+ * The suggestion names the exception rather than hiding it, which is what keeps
2501
+ * the finding honest at `audit` — and what makes this the endpoint rule a reader
2502
+ * may reasonably overrule.
2503
+ */
2504
+ const controlFlowEndpoints = {
2505
+ id: 'uml.control-flow-endpoints',
2506
+ framework: 'uml',
2507
+ family: 'relation-endpoints',
2508
+ severity: 'audit',
2509
+ roles: UML_ROLES,
2510
+ messageKey: 'com.labre.uml.validation.control-flow-endpoints',
2511
+ messageFallback: 'This control flow runs between artefacts an activity does not route control through.',
2512
+ suggestionKey: 'com.labre.uml.validation.control-flow-endpoints.suggestion',
2513
+ suggestionFallback: 'A control flow joins actions, control nodes and the signal artefacts. Draw an object flow if what travels is data, a transition if these are states — and if this really is a control-typed object node, the notation has no way of saying so and the remark can be waived.',
2514
+ version: 1,
2515
+ provenance: {
2516
+ source: 'standard',
2517
+ reference: 'OMG UML 2.5.1 §15.7.9.4 — "ControlFlows may not have ObjectNodes at either end, except for ObjectNodes with control type"',
2518
+ },
2519
+ backgroundRole: UML_ROLE.diagram,
2520
+ endpoints: {
2521
+ edgeRole: UML_ROLE['control-flow'],
2522
+ allowed: UML_CONTROL_FLOW_MATRIX,
2523
+ },
2524
+ };
2525
+ /**
2526
+ * **U31** — an object flow that touches no data.
2527
+ *
2528
+ * §15.7.11.4's `same_upper_bounds` describes an object flow as a line between
2529
+ * ObjectNodes "with optionally intervening ControlNodes", so at least one end of
2530
+ * one is data. See {@link UML_OBJECT_FLOW_MATRIX} for why the reading is the
2531
+ * loose one, why the rule is a `recommendation` and why `uml.strict` is the one
2532
+ * endpoint rule it leaves alone: §15.4.4's elided PINS make the action-to-action
2533
+ * drawing a legal shorthand, and this canvas has no pin to draw.
2534
+ */
2535
+ const objectFlowEndpoints = {
2536
+ id: 'uml.object-flow-endpoints',
2537
+ framework: 'uml',
2538
+ family: 'relation-endpoints',
2539
+ severity: 'audit',
2540
+ roles: UML_ROLES,
2541
+ messageKey: 'com.labre.uml.validation.object-flow-endpoints',
2542
+ messageFallback: 'Neither end of this object flow is a data node.',
2543
+ suggestionKey: 'com.labre.uml.validation.object-flow-endpoints.suggestion',
2544
+ suggestionFallback: 'An object flow carries something: draw the object node it carries between the two actions, and name what travels. If what you meant is "this happens, then that happens", draw a control flow instead.',
2545
+ version: 1,
2546
+ provenance: {
2547
+ source: 'recommendation',
2548
+ reference: 'OMG UML 2.5.1 §15.7.11.4 — ObjectNodes connected by an ObjectFlow, with optionally intervening ControlNodes; §15.4.4 elides the Pins that would be those nodes, so the action-to-action drawing is a shorthand rather than a defect',
2549
+ },
2550
+ backgroundRole: UML_ROLE.diagram,
2551
+ endpoints: {
2552
+ edgeRole: UML_ROLE['object-flow'],
2553
+ allowed: UML_OBJECT_FLOW_MATRIX,
2554
+ },
2555
+ };
2556
+ /**
2557
+ * **U32** — a transition drawn onto something that is not a vertex.
2558
+ *
2559
+ * §14.5.12: a Transition runs from one Vertex to another, and a Vertex is a
2560
+ * State, a FinalState or a Pseudostate. The finding worth having is the one the
2561
+ * alphabet entries buy — a transition dragged onto a DECISION diamond, which is
2562
+ * §15.3.4's branch where §14.2.4's is the `<<choice>>`, and the commonest
2563
+ * confusion between these two sheets by a distance.
2564
+ *
2565
+ * See {@link UML_TRANSITION_MATRIX} for why the table over the vertices is the
2566
+ * full square rather than a grammar: the counts are owned by the degree rules,
2567
+ * and one wrong arrow gets one bracket.
2568
+ */
2569
+ const transitionEndpoints = {
2570
+ id: 'uml.transition-endpoints',
2571
+ framework: 'uml',
2572
+ family: 'relation-endpoints',
2573
+ severity: 'audit',
2574
+ roles: UML_ROLES,
2575
+ messageKey: 'com.labre.uml.validation.transition-endpoints',
2576
+ messageFallback: 'This transition runs between artefacts a state machine has no transitions between.',
2577
+ suggestionKey: 'com.labre.uml.validation.transition-endpoints.suggestion',
2578
+ suggestionFallback: 'A transition joins states, final states and the pseudostates that route between them. A diamond on a state machine is the choice pseudostate, not the activity decision — swap it, or draw a control flow if this sheet is really an activity.',
2579
+ version: 1,
2580
+ provenance: {
2581
+ source: 'standard',
2582
+ reference: 'OMG UML 2.5.1 §14.5.12 — "A Transition is a single directed arc originating from a single source Vertex and terminating on a single target Vertex"',
2583
+ },
2584
+ backgroundRole: UML_ROLE.diagram,
2585
+ endpoints: {
2586
+ edgeRole: UML_ROLE.transition,
2587
+ allowed: UML_TRANSITION_MATRIX,
2588
+ },
2589
+ };
2590
+ /* ── Behaviour: can the flow get there at all? ──────────────────────────── */
2591
+ /**
2592
+ * **U33 / U34** — an action, or a state, no walk from the beginning ever
2593
+ * reaches.
2594
+ *
2595
+ * The orphan question, and the first one this pack asks that no amount of
2596
+ * looking at an element or at one line can answer: an action with a perfectly
2597
+ * good control flow on either side is still unreachable if the chain it belongs
2598
+ * to never starts anywhere. BPMN's `unreachable-step` is the same rule one
2599
+ * notation over, and the reasoning it records applies here whole.
2600
+ *
2601
+ * `implicitRoots` is on, and it is what makes the rules shippable: a node nothing
2602
+ * points at IS a beginning whether or not anybody drew the disc, so a second
2603
+ * branch drawn beside the first — and every activity in the thirty seconds before
2604
+ * its initial node goes down — is left alone. What survives is the real defect: a
2605
+ * RING entered from nowhere, where every step is pointed at and no walk reaches
2606
+ * any of them.
2607
+ *
2608
+ * ## Two rules, for the two vocabularies
2609
+ *
2610
+ * `reachability` follows ONE edge role, so the activity walk (control flow) and
2611
+ * the state machine walk (transition) are two declarations. Both start from
2612
+ * `uml:initial`, which is the one role the two sheets genuinely share, and both
2613
+ * are silent on a board carrying none of either kind of root.
2614
+ *
2615
+ * ## The subjects are the ACTION and the STATE, and nothing else
2616
+ *
2617
+ * `reachability` names one subject role. The object nodes, the signal artefacts
2618
+ * and the pseudostates are not walked for — a narrower rule that is right is
2619
+ * worth more than a wider one that guesses, and the artefact a reader asks "how
2620
+ * do we get here?" about is the step and the state.
2621
+ *
2622
+ * ## `on-demand`, for BPMN's reason exactly
2623
+ *
2624
+ * A graph sweep is O(V + E), rebuilds its adjacency every evaluation and cannot
2625
+ * be made incremental even in principle — reachability is global, so re-pointing
2626
+ * one arrow can orphan or rescue an arbitrary number of steps nowhere near it. It
2627
+ * stays out of the drawing budget and runs when somebody asks whether the diagram
2628
+ * is finished, which is also the only moment the answer is worth anything.
2629
+ */
2630
+ const unreachableAction = {
2631
+ id: 'uml.unreachable-action',
2632
+ framework: 'uml',
2633
+ family: 'reachability',
2634
+ severity: 'audit',
2635
+ roles: UML_ROLES,
2636
+ messageKey: 'com.labre.uml.validation.unreachable-action',
2637
+ messageFallback: 'No flow from the beginning of this activity reaches here.',
2638
+ suggestionKey: 'com.labre.uml.validation.unreachable-action.suggestion',
2639
+ suggestionFallback: 'Follow the control flows back from this action: somewhere the chain stops, or an arrow points the wrong way. Join it to the flow, or delete it if it no longer happens.',
2640
+ version: 1,
2641
+ provenance: {
2642
+ source: 'recommendation',
2643
+ reference: 'Best practice — OMG UML 2.5.1 §15.3.3 seeds the flow at the InitialNodes and requires the reachability of nobody',
2644
+ },
2645
+ moment: 'on-demand',
2646
+ backgroundRole: UML_ROLE.diagram,
2647
+ reachability: {
2648
+ rootRole: UML_ROLE.initial,
2649
+ subjectRole: UML_ROLE.action,
2650
+ edgeRole: UML_ROLE['control-flow'],
2651
+ // An action nothing points at IS a beginning, whether or not anybody drew
2652
+ // the disc — see the header.
2653
+ implicitRoots: true,
2654
+ },
2655
+ };
2656
+ const unreachableState = {
2657
+ id: 'uml.unreachable-state',
2658
+ framework: 'uml',
2659
+ family: 'reachability',
2660
+ severity: 'audit',
2661
+ roles: UML_ROLES,
2662
+ messageKey: 'com.labre.uml.validation.unreachable-state',
2663
+ messageFallback: 'No transition from the beginning of this machine reaches here.',
2664
+ suggestionKey: 'com.labre.uml.validation.unreachable-state.suggestion',
2665
+ suggestionFallback: 'Follow the transitions back from this state: somewhere the chain stops, or an arrow points the wrong way. Join it to the machine, or delete it if the machine can no longer be in it.',
2666
+ version: 1,
2667
+ provenance: {
2668
+ source: 'recommendation',
2669
+ reference: 'Best practice — OMG UML 2.5.1 §14.2.3 seeds execution at the initial Pseudostate and requires the reachability of nobody',
2670
+ },
2671
+ moment: 'on-demand',
2672
+ backgroundRole: UML_ROLE.diagram,
2673
+ reachability: {
2674
+ rootRole: UML_ROLE.initial,
2675
+ subjectRole: UML_ROLE.state,
2676
+ edgeRole: UML_ROLE.transition,
2677
+ implicitRoots: true,
2678
+ },
2679
+ };
2680
+ /* ── The interaction: what a message may run between ────────────────────── */
2681
+ /**
2682
+ * The two artefacts a message may start from and land on — §17.4.4's sender and
2683
+ * receiver MessageEnds, as this canvas draws them.
2684
+ *
2685
+ * A LIFELINE is the participant itself, and an EXECUTION is the bar drawn on its
2686
+ * spine while it is doing something (§17.3.4: "apply a thin gray or white
2687
+ * rectangle that covers the Lifeline line"). Both are legal ends of the same
2688
+ * arrow, and which of the two an author aims at is a matter of how much detail
2689
+ * the drawing is carrying: the same call is drawn lifeline-to-lifeline on a
2690
+ * sketch and execution-to-execution once the activations are in. A grammar that
2691
+ * admitted one and not the other would indict half the sequence diagrams ever
2692
+ * drawn for being drawn at the wrong level of detail.
2693
+ *
2694
+ * The DESTRUCTION is deliberately not here: it is not an end a message may
2695
+ * generally reach, it is the end a DELETE message must reach, which is a
2696
+ * sentence of its own below.
2697
+ */
2698
+ const UML_MESSAGE_ENDS = [
2699
+ UML_ROLE.lifeline,
2700
+ UML_ROLE.execution,
2701
+ ];
2702
+ /**
2703
+ * The three message sorts whose ends §17.4.4 constrains no further than "a
2704
+ * sender and a receiver": a synchronous call, an asynchronous one and a reply.
2705
+ */
2706
+ const UML_PLAIN_MESSAGE_ROLES = [
2707
+ UML_ROLE['message-sync'],
2708
+ UML_ROLE['message-async'],
2709
+ UML_ROLE['message-reply'],
2710
+ ];
2711
+ /**
2712
+ * ALPHABET entries for the message grammar — the class-side and use-case
2713
+ * vocabularies, carried as true sentences of THEIR OWN edges.
2714
+ *
2715
+ * The device {@link UML_STATE_ALPHABET} explains, one sheet further on: these
2716
+ * triplets carry `uml:association`, so `inMatrix` — which matches a triplet's
2717
+ * edge with `roleIsA` — can never let one sanction a message. Their whole effect
2718
+ * is to put `uml:classifier`, `uml:actor` and `uml:use-case` into the alphabet
2719
+ * the message rule speaks, which is what makes "a message dragged onto a class"
2720
+ * and "a message dragged onto an actor" findings rather than silence.
2721
+ *
2722
+ * The ACTOR is the one that earns its place. Every drawing tool in the world
2723
+ * puts a stick figure at the left of a sequence diagram, and §17.3.4 does give a
2724
+ * lifeline head "a shape that is based on the classifier for the part" — so an
2725
+ * author reaching for the actor tool on an `sd` sheet is making a reasonable
2726
+ * mistake about THIS canvas, where a participant is a lifeline whatever it
2727
+ * represents. Being in the alphabet is what lets the finding say so.
2728
+ */
2729
+ const UML_INTERACTION_ALPHABET = [
2730
+ {
2731
+ source: UML_ROLE.actor,
2732
+ edge: UML_ROLE.association,
2733
+ target: UML_ROLE['use-case'],
2734
+ },
2735
+ {
2736
+ source: UML_ROLE.classifier,
2737
+ edge: UML_ROLE.association,
2738
+ target: UML_ROLE.classifier,
2739
+ },
2740
+ ];
2741
+ /**
2742
+ * What a MESSAGE may run between, sort by sort — §17.4.4, which is the one place
2743
+ * in Clause 17 where the notation constrains an ARROW's ends rather than its
2744
+ * shape.
2745
+ *
2746
+ * Three of the five sorts are the full square over {@link UML_MESSAGE_ENDS}: a
2747
+ * call, a signal and a reply run from a participant to a participant, and the
2748
+ * clause says nothing more. The other two each carry a sentence the
2749
+ * specification states outright:
2750
+ *
2751
+ * - a **create** message "has a dashed line with an open arrow head" and is
2752
+ * drawn to the HEAD of the lifeline it brings into existence (§17.4.4,
2753
+ * Figure 17.13), so its target is a LIFELINE and never an execution — there
2754
+ * is nothing to be executing on a participant that does not exist yet;
2755
+ * - a **delete** message "must end in a DestructionOccurrenceSpecification"
2756
+ * (§17.4.4, quoted in the rule's provenance), which is the X this canvas
2757
+ * draws as `uml:destruction`. The LIFELINE is admitted beside it, and that is
2758
+ * a deliberate tolerance rather than a reading of the clause: an author draws
2759
+ * the arrow first and drops the cross on its end afterwards, and a grammar
2760
+ * that indicted the intermediate state would be indicting the gesture.
2761
+ * `uml.strict` promotes this rule, so the tolerance is the same at both
2762
+ * levels — the finding that is worth having is the delete drawn onto an
2763
+ * EXECUTION, which is a participant carrying on after it has been destroyed.
2764
+ *
2765
+ * Self-loops are NOT forbidden, and §17.4.4 is explicit about why: "The send and
2766
+ * receive events may both be on the same lifeline." A participant calling itself
2767
+ * is the notation's own figure for a nested activation.
2768
+ */
2769
+ export const UML_MESSAGE_MATRIX = [
2770
+ ...UML_PLAIN_MESSAGE_ROLES.flatMap(edge => UML_MESSAGE_ENDS.flatMap(source => UML_MESSAGE_ENDS.map(target => ({ source, edge, target })))),
2771
+ ...UML_MESSAGE_ENDS.map(source => ({
2772
+ source,
2773
+ edge: UML_ROLE['message-create'],
2774
+ target: UML_ROLE.lifeline,
2775
+ })),
2776
+ ...UML_MESSAGE_ENDS.flatMap(source => [UML_ROLE.destruction, UML_ROLE.lifeline].map(target => ({
2777
+ source,
2778
+ edge: UML_ROLE['message-delete'],
2779
+ target,
2780
+ }))),
2781
+ ...UML_INTERACTION_ALPHABET,
2782
+ ];
2783
+ /**
2784
+ * **U39** — a message drawn between artefacts an interaction does not exchange
2785
+ * messages between.
2786
+ *
2787
+ * `edgeRole: uml:message` — the PARENT of the five sorts — so one rule covers a
2788
+ * call, a signal, a reply, a create and a delete, and the matrix above is what
2789
+ * tells them apart: each triplet names a specialised edge role, and `inMatrix`
2790
+ * matches it with `roleIsA`, so a create's sentence can never sanction a delete.
2791
+ *
2792
+ * One id rather than five, for the reason every other endpoint rule in this file
2793
+ * is one id: the five sorts are one grammar with one gesture to fix it — re-point
2794
+ * an end — and five rules would be five lines in both profile tables and five
2795
+ * sentences saying the same thing about the same drag.
2796
+ *
2797
+ * ## What it stays silent about
2798
+ *
2799
+ * A message with a free end, a message onto a NOTE, onto a sticky, onto a
2800
+ * rectangle somebody thought with: the alphabet is the participants plus the two
2801
+ * foreign vocabularies {@link UML_INTERACTION_ALPHABET} names, and everything
2802
+ * outside it is a sketch (PRD principle 8). And a message from a participant to
2803
+ * ITSELF, which §17.4.4 draws on purpose.
2804
+ */
2805
+ const messageEndpoints = {
2806
+ id: 'uml.message-endpoints',
2807
+ framework: 'uml',
2808
+ family: 'relation-endpoints',
2809
+ severity: 'audit',
2810
+ roles: UML_ROLES,
2811
+ messageKey: 'com.labre.uml.validation.message-endpoints',
2812
+ messageFallback: 'This message runs between artefacts an interaction does not exchange messages between.',
2813
+ suggestionKey: 'com.labre.uml.validation.message-endpoints.suggestion',
2814
+ suggestionFallback: 'A message joins two participants — a lifeline, or the execution bar drawn on one. A create message lands on the head of the lifeline it brings into existence, and a delete message ends on the cross that destroys it. If this participant is an actor, draw it as a lifeline: a sequence sheet has one kind of participant.',
2815
+ version: 1,
2816
+ provenance: {
2817
+ source: 'standard',
2818
+ reference: 'OMG UML 2.5.1 §17.4.4 — "An object deletion Message (messageSort equals deleteMessage) must end in a DestructionOccurrenceSpecification", and §17.3.4 on what a Lifeline and an ExecutionSpecification are',
2819
+ },
2820
+ backgroundRole: UML_ROLE.diagram,
2821
+ endpoints: {
2822
+ edgeRole: UML_ROLE.message,
2823
+ allowed: UML_MESSAGE_MATRIX,
2824
+ },
2825
+ };
2826
+ /**
2827
+ * **U40** — a message label that does not parse.
2828
+ *
2829
+ * The fifth `label-syntax` rule (ADR 0021), and the first written on a CONNECTOR's
2830
+ * centre label since `uml.transition-syntax`. `appliesTo: uml:message` reaches
2831
+ * all five sorts for free — they specialise it — which is exactly right here:
2832
+ * §17.4.4 prints ONE label grammar for the request sorts and one for the reply,
2833
+ * the second being the first plus an assignment target and a return value, and
2834
+ * `checkMessageLabel` reads both because an arrow carries one text and nothing
2835
+ * on the canvas tells a reader which of the two productions to use.
2836
+ *
2837
+ * `perLine: false`, like the transition label and for the same reason: §17.4.4's
2838
+ * label is a single production, not a list, and an author who wraps
2839
+ * `r = place(order, now) : Receipt` onto a second line has written one label.
2840
+ *
2841
+ * ## What it stays silent about
2842
+ *
2843
+ * An UNLABELLED arrow — §17.4.4 makes the label optional and a sequence diagram
2844
+ * is drawn arrows-first — and `*`, which is a spelling the clause prints. See
2845
+ * `grammar.ts` for the four things it does report, each of them a case where the
2846
+ * lenient parse the exporters read LOST something the author typed.
2847
+ */
2848
+ const messageSyntax = {
2849
+ id: 'uml.message-syntax',
2850
+ framework: 'uml',
2851
+ family: 'label-syntax',
2852
+ severity: 'audit',
2853
+ appliesTo: UML_ROLE.message,
2854
+ roles: UML_ROLES,
2855
+ messageKey: 'com.labre.uml.validation.message-syntax',
2856
+ messageFallback: 'This message label does not parse:',
2857
+ suggestionKey: 'com.labre.uml.validation.message-syntax.suggestion',
2858
+ suggestionFallback: 'A message is labelled "place(order)", and a reply "r = place(order) : Receipt" — the assignment, the arguments and the returned value are each optional, and an arrow with no label at all is legal.',
2859
+ version: 1,
2860
+ provenance: {
2861
+ source: 'standard',
2862
+ reference: 'OMG UML 2.5.1 §17.4.4 — <request-message-label> ::= <message-name> [( [<input-argument-list>] )], <reply-message-label> ::= [<assignment-target> =] <message-name> [( [<output-argument-list>] )] [: <value-specification>]',
2863
+ },
2864
+ moment: 'on-demand',
2865
+ backgroundRole: UML_ROLE.diagram,
2866
+ labelSyntax: { parse: checkMessageLabel, perLine: false },
2867
+ };
2868
+ /**
2869
+ * **U41** — a lifeline whose head has been emptied.
2870
+ *
2871
+ * The THIRD `label-presence` rule, and the one that made the tier vocabulary
2872
+ * grow: until phase 3 a lifeline's head carried `uml:label`, the actor's and the
2873
+ * use case's tier, and a rule written on that tier could not tell the three
2874
+ * apart. Which made both halves of the naming question wrong at once — an
2875
+ * emptied head was reported as "this actor or use case has no name", and an
2876
+ * author of a sequence diagram was told to write "Customer" in a box that wants
2877
+ * `order : Order`. `uml:lifeline-ident` is that tier split off (`roles.ts`), and
2878
+ * this is one of the two rules the split exists for.
2879
+ *
2880
+ * ONE rule, one subject role, and no overlap with {@link unnamedActorOrUseCase}
2881
+ * by construction: the new role is FLAT, so `roleIsA` never walks from one to
2882
+ * the other and an emptied head is reported here and nowhere else.
2883
+ *
2884
+ * ## What it stays silent about
2885
+ *
2886
+ * A head somebody deleted outright rather than emptied — the same known limit
2887
+ * {@link unnamedClassifier} documents, and for the same reason: there is no text
2888
+ * on the sheet for a `label-presence` rule to be about.
2889
+ */
2890
+ const unnamedLifeline = {
2891
+ id: 'uml.unnamed-lifeline',
2892
+ framework: 'uml',
2893
+ family: 'label-presence',
2894
+ severity: 'audit',
2895
+ appliesTo: UML_ROLE['lifeline-ident'],
2896
+ roles: UML_ROLES,
2897
+ messageKey: 'com.labre.uml.validation.unnamed-lifeline',
2898
+ messageFallback: 'This lifeline has no participant in its head.',
2899
+ suggestionKey: 'com.labre.uml.validation.unnamed-lifeline.suggestion',
2900
+ suggestionFallback: 'Write who is taking part — "order : Order", "customer", or just ": Order" for an anonymous instance. Every arrow on this sheet is read as a sentence about the head it leaves and the head it reaches, and an empty one leaves both halves unsaid.',
2901
+ version: 1,
2902
+ provenance: {
2903
+ source: 'recommendation',
2904
+ reference: 'OMG UML 2.5.1 §17.3.4 — a Lifeline is drawn as a named rectangle with a dashed line descending from it',
2905
+ },
2906
+ // On-demand for the reason the other two naming rules are: a head is what a
2907
+ // user changes by TYPING, and real-time would re-evaluate on every keystroke.
2908
+ moment: 'on-demand',
2909
+ backgroundRole: UML_ROLE.diagram,
2910
+ label: { present: true },
2911
+ };
2912
+ /**
2913
+ * **U42** — a lifeline head that does not parse.
2914
+ *
2915
+ * The SIXTH `label-syntax` rule (ADR 0021), and the second half of the tier
2916
+ * split. §17.3.4 prints a BNF — `<lifelineident> ::= ([<connectable-element-name>
2917
+ * [\[<selector>\]]] [: <class-name>] | 'self')` — which is exactly the kind of
2918
+ * claim this family exists to make, and `checkLifelineIdent` is the checker the
2919
+ * IMPORTERS and the two exporters already read the head with. So a finding here
2920
+ * is the line that will leave the sheet less structured than it was drawn: a
2921
+ * head the grammar cannot split is a lifeline whose `name` and `type` go into
2922
+ * the XMI and the PlantUML as one opaque string.
2923
+ *
2924
+ * `perLine: false`, like the transition and the message labels: §17.3.4's
2925
+ * production is one line, not a list, and a head wrapped onto two is one head.
2926
+ *
2927
+ * ## Why it could not be written on `uml:label`
2928
+ *
2929
+ * `Place an order (fast)` is a perfectly good use case name and a broken
2930
+ * lifeline head — parentheses in a head read as a message label, which is what
2931
+ * `checkLifelineIdent` says about them. A rule on the shared tier would have
2932
+ * indicted every parenthesised ellipse on every use case diagram in the library
2933
+ * (`roles.ts`).
2934
+ *
2935
+ * ## What it stays silent about
2936
+ *
2937
+ * An EMPTY head, which is {@link unnamedLifeline}'s question and carries its own
2938
+ * sentence — two rules on one word to fix is the thing this pack refuses
2939
+ * everywhere — and `self`, which §17.3.4 prints as an alternative of the
2940
+ * production itself.
2941
+ */
2942
+ const lifelineIdentSyntax = {
2943
+ id: 'uml.lifeline-ident-syntax',
2944
+ framework: 'uml',
2945
+ family: 'label-syntax',
2946
+ severity: 'audit',
2947
+ appliesTo: UML_ROLE['lifeline-ident'],
2948
+ roles: UML_ROLES,
2949
+ messageKey: 'com.labre.uml.validation.lifeline-ident-syntax',
2950
+ messageFallback: 'This lifeline head does not parse:',
2951
+ suggestionKey: 'com.labre.uml.validation.lifeline-ident-syntax.suggestion',
2952
+ suggestionFallback: 'A head is written "name : Class" — "order : Order", "customer", ": Order" for an anonymous participant, "customers[i] : Customer" for one of many, or "self". The name and the class are each optional; the colon between them is not decoration.',
2953
+ version: 1,
2954
+ provenance: {
2955
+ source: 'standard',
2956
+ reference: "OMG UML 2.5.1 §17.3.4 — <lifelineident> ::= ([<connectable-element-name> ['[' <selector> ']']] [: <class-name>] | 'self')",
2957
+ },
2958
+ moment: 'on-demand',
2959
+ backgroundRole: UML_ROLE.diagram,
2960
+ labelSyntax: { parse: checkLifelineIdent, perLine: false },
2961
+ };
2962
+ /**
2963
+ * The pack, whole: forty-three rules over ten families.
2964
+ *
2965
+ * Sixteen in phase 1, and not that brief's seventeen ids because the actor's
2966
+ * name and the use case's name are ONE rule: they are the same tier role, and
2967
+ * two rules on one role report one emptied word twice (see
2968
+ * {@link unnamedActorOrUseCase}). The structural sheets appended three — the
2969
+ * grammars of the three edges they draw — and skipped two the engine could not
2970
+ * be asked; one of those two came back as the forty-third rule below, once the
2971
+ * engine grew the family it needed. The BEHAVIOUR sheets appended fifteen, which is the largest single
2972
+ * addition this library has taken: an activity and a state machine are the two
2973
+ * diagrams UML constrains arithmetically, so where a class diagram's pack is
2974
+ * mostly a vocabulary these are counts, degrees and a graph walk.
2975
+ *
2976
+ * The SEQUENCE sheet appended four, and all four are families this pack already
2977
+ * had — which says something about the notation rather than about the effort:
2978
+ * an interaction's whole grammar is "who may exchange a message with whom",
2979
+ * "what a message label says", and the two questions any tier is asked about
2980
+ * the words in a lifeline's head. Those last two are the ones that cost a role:
2981
+ * `uml:lifeline-ident` exists because §17.3.4 prints a BNF for a head and
2982
+ * §18.1.4 prints none for an actor's word, and one tier carrying both would have
2983
+ * had to ask them the same question (`roles.ts`, {@link unnamedLifeline}). The
2984
+ * four requirements Clause 17 states that this library CANNOT state — a message
2985
+ * never traversed upwards (§17.4.4), an execution bar sat on a lifeline's spine
2986
+ * (§17.3.4), a create message landing on a head, an operand's guard written in
2987
+ * brackets (§17.6.4) — are each a geometry no family expresses, and they are
2988
+ * recorded in `docs/adr/0022` rather than approximated here. A rule that fires
2989
+ * on the wrong thing costs more than a requirement nobody checks.
2990
+ *
2991
+ * The PO recette of 2026-09-16 appended the forty-third, and it is the one rule
2992
+ * in the pack that was written down as impossible before it was written: a port
2993
+ * dragged into the middle of its component (§11.3.4). See {@link portOnBorder},
2994
+ * and the paragraph above the six remaining gaps on how it left that list.
2995
+ *
2996
+ * ## TWO of the ten families are new to the ENGINE, and they are the only ones
2997
+ *
2998
+ * `label-syntax` (`docs/adr/0021`) arrived with four spelling rules and carries
2999
+ * six, and it was the first family this library added for a reason no table
3000
+ * could meet: a notation's GRAMMAR is a parser, and the clauses these rules cite
3001
+ * are already written as one in `grammar.ts` for the exporters.
3002
+ * `border-proximity` (`docs/adr/0024`) is the second, and its reason is the
3003
+ * opposite one — pure geometry that three existing families each get NEARLY
3004
+ * right and none of them states: a distance from a glyph's centre to another
3005
+ * artefact's outline. In both cases the family owns the walk and the pack owns
3006
+ * the reading, and in both cases the alternative on the table was approximating
3007
+ * the requirement with a neighbouring family. Everything else the thirty-odd
3008
+ * roles of phases 2 and 3 needed, the engine already had.
3009
+ *
3010
+ * ## The other eight are the engine's, and two of them are BPMN's
3011
+ *
3012
+ * `role-count` and `reachability` are BPMN's, registered here with UML's own
3013
+ * roles and nothing else: a state machine with two beginnings and a ring of
3014
+ * states nothing enters are the same two questions a pool with two start events
3015
+ * and an unreachable task ask. So the claim `docs/add-a-framework` makes about
3016
+ * the seam survives the largest notation in the library twice over — the
3017
+ * fifteen edge roles are fifteen readings of `relation-endpoints`, the four
3018
+ * frames are the membership families C4 already uses, and the sheet's own
3019
+ * declaration is the `view-admissibility` C4 opened.
3020
+ *
3021
+ * ## Several rules say one sentence twice, and the reason is always the same
3022
+ *
3023
+ * Five pairs — the two histories, the two initial-node counts, the two finals,
3024
+ * the two reachability walks — are one requirement declared twice because the
3025
+ * family that answers it names ONE role: one subject role, or one edge role.
3026
+ * They never double-report (an element carries one role, an edge carries one
3027
+ * role, and each pair's two halves are flat siblings), and each half carries the
3028
+ * sentence and the authority its own half of the notation has. Where the pair
3029
+ * could have been collapsed by inventing an ancestor role, it was not: this
3030
+ * vocabulary's whole authority is that every role in it is the specification's.
3031
+ */
3032
+ export const UML_RULES = [
3033
+ // Membership: is the drawing on the sheet it claims to be on?
3034
+ elementOutsideFrame,
3035
+ notAdmissibleOnKind,
3036
+ useCaseOutsideSubject,
3037
+ actorInsideSubject,
3038
+ // Naming: does the drawing say anything at all?
3039
+ unnamedClassifier,
3040
+ unnamedActorOrUseCase,
3041
+ // Spelling: and is what it says written the way the clause writes it?
3042
+ attributeSyntax,
3043
+ operationSyntax,
3044
+ transitionSyntax,
3045
+ multiplicitySyntax,
3046
+ // Grammar: what each line may run between.
3047
+ generalizationEndpoints,
3048
+ generalizationSelfLoop,
3049
+ realizationEndpoints,
3050
+ dependencyOnObject,
3051
+ includeEndpoints,
3052
+ extendEndpoints,
3053
+ actorActorAssociation,
3054
+ deployEndpoints,
3055
+ manifestEndpoints,
3056
+ communicationPathEndpoints,
3057
+ untypedEdge,
3058
+ // Degree: how many lines may reach one artefact?
3059
+ compositionSingleOwner,
3060
+ useCaseNoActor,
3061
+ // Attachment: is the glyph still on the box it was drawn on?
3062
+ portOnBorder,
3063
+ // Behaviour — membership: is the drawing in the lane, in the region?
3064
+ nodeInPartition,
3065
+ shallowHistoryOutsideRegion,
3066
+ deepHistoryOutsideRegion,
3067
+ // Behaviour — counts: how many beginnings, how many lines reach one glyph?
3068
+ initialSingle,
3069
+ initialNoIncomingFlow,
3070
+ initialNoIncomingTransition,
3071
+ activityFinalNoOutgoing,
3072
+ flowFinalNoOutgoing,
3073
+ finalStateNoOutgoing,
3074
+ decisionIncomingCount,
3075
+ // Behaviour — grammar: what each flow may run between.
3076
+ controlFlowEndpoints,
3077
+ objectFlowEndpoints,
3078
+ transitionEndpoints,
3079
+ // Behaviour — the graph: can the flow get there at all?
3080
+ unreachableAction,
3081
+ unreachableState,
3082
+ // The interaction: what a message runs between, and what its label says…
3083
+ messageEndpoints,
3084
+ messageSyntax,
3085
+ // …and the two questions a lifeline's own head is asked, which are the two
3086
+ // questions every tier in this pack is asked: does it say anything, and does
3087
+ // what it says parse.
3088
+ unnamedLifeline,
3089
+ lifelineIdentSyntax,
3090
+ ];