dsh-model-everything 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en-US.md +332 -0
- package/README.md +332 -3
- package/cordis.patch.yml +20 -0
- package/examples/README.md +48 -0
- package/examples/control-dispatcher.json +113 -0
- package/examples/control-dispatcher.overbudget.json +113 -0
- package/examples/ecommerce-data-model.json +185 -0
- package/examples/order-state-machine.after.json +116 -0
- package/examples/order-state-machine.before.json +101 -0
- package/examples/user-migration.after.json +29 -0
- package/examples/user-migration.before.json +29 -0
- package/gate/locale/en.json +6 -0
- package/gate/locale/zh.json +6 -0
- package/gate/package.json +5 -0
- package/lib/baseline.js +249 -0
- package/lib/compose-tool.js +80 -0
- package/lib/concurrency-tool.js +40 -0
- package/lib/concurrency.js +206 -0
- package/lib/data-engine.js +1141 -0
- package/lib/data-tool.js +87 -0
- package/lib/design-engine.js +1149 -0
- package/lib/design-tool.js +63 -0
- package/lib/engine.js +4343 -0
- package/lib/export-tool.js +78 -0
- package/lib/exporters.js +434 -0
- package/lib/gate.js +274 -0
- package/lib/granularity.js +237 -0
- package/lib/index.js +237 -0
- package/lib/json-value.js +18 -0
- package/lib/model-file.js +48 -0
- package/lib/option-shape.js +62 -0
- package/lib/report-diff-tool.js +63 -0
- package/lib/report-view.js +433 -0
- package/lib/structure-tool.js +91 -0
- package/lib/structure.js +711 -0
- package/lib/tool.js +94 -0
- package/lib/types/baseline.d.ts +65 -0
- package/lib/types/compose-tool.d.ts +7 -0
- package/lib/types/concurrency-tool.d.ts +8 -0
- package/lib/types/concurrency.d.ts +37 -0
- package/lib/types/data-engine.d.ts +211 -0
- package/lib/types/data-tool.d.ts +10 -0
- package/lib/types/design-engine.d.ts +215 -0
- package/lib/types/design-tool.d.ts +11 -0
- package/lib/types/engine.d.ts +494 -0
- package/lib/types/export-tool.d.ts +7 -0
- package/lib/types/exporters.d.ts +37 -0
- package/lib/types/gate.d.ts +91 -0
- package/lib/types/granularity.d.ts +106 -0
- package/lib/types/index.d.ts +45 -0
- package/lib/types/json-value.d.ts +21 -0
- package/lib/types/model-file.d.ts +7 -0
- package/lib/types/option-shape.d.ts +22 -0
- package/lib/types/report-diff-tool.d.ts +8 -0
- package/lib/types/report-view.d.ts +50 -0
- package/lib/types/structure-tool.d.ts +10 -0
- package/lib/types/structure.d.ts +152 -0
- package/lib/types/tool.d.ts +11 -0
- package/lib/types/uml-communication.d.ts +109 -0
- package/lib/types/uml-composite.d.ts +107 -0
- package/lib/types/uml-interaction-overview.d.ts +91 -0
- package/lib/types/uml-profile.d.ts +112 -0
- package/lib/types/uml-structure.d.ts +53 -0
- package/lib/types/uml-syntax.d.ts +296 -0
- package/lib/types/uml-timing.d.ts +152 -0
- package/lib/types/uml-tool.d.ts +12 -0
- package/lib/types/uml-usecase.d.ts +58 -0
- package/lib/types/uml.d.ts +303 -0
- package/lib/uml-communication.js +353 -0
- package/lib/uml-composite.js +442 -0
- package/lib/uml-interaction-overview.js +277 -0
- package/lib/uml-profile.js +373 -0
- package/lib/uml-structure.js +306 -0
- package/lib/uml-syntax.js +545 -0
- package/lib/uml-timing.js +974 -0
- package/lib/uml-tool.js +213 -0
- package/lib/uml-usecase.js +532 -0
- package/lib/uml.js +2110 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +128 -6
- package/skills/model-everything/SKILL.md +450 -0
- package/skills/model-everything/references/dsh-model-schema.md +437 -0
- package/skills/model-everything/references/gap-routing-guide.md +42 -0
- package/skills/model-everything/references/hash-spec.md +152 -0
- package/skills/model-everything/references/logic-verification-guide.md +422 -0
- package/skills/model-everything-datamodel/SKILL.md +130 -0
- package/skills/model-everything-datamodel/references/data-model-guide.md +84 -0
- package/skills/model-everything-datamodel/references/data-model-schema.md +128 -0
- package/skills/model-everything-design/SKILL.md +115 -0
- package/skills/model-everything-interleaving/SKILL.md +63 -0
- package/skills/model-everything-interleaving/SKILL.zh-CN.md +60 -0
- package/skills/model-everything-interleaving/references/concurrency-risk-guide.md +63 -0
- package/skills/model-everything-report-reading/SKILL.md +137 -0
- package/skills/model-everything-report-reading/references/reading-a-report.md +166 -0
- package/skills/model-everything-report-reading/references/reading-a-report.mmd +59 -0
- package/skills/model-everything-structure/SKILL.md +96 -0
- package/skills/model-everything-structure/references/structure-review-guide.md +201 -0
- package/skills/model-everything-uml/SKILL.md +60 -0
- package/skills/model-everything-uml/references/uml-modeling-guide.md +551 -0
- package/src/baseline.ts +288 -0
- package/src/compose-tool.ts +82 -0
- package/src/concurrency-tool.ts +44 -0
- package/src/concurrency.ts +254 -0
- package/src/data-engine.ts +1208 -0
- package/src/data-tool.ts +91 -0
- package/src/design-engine.ts +1263 -0
- package/src/design-tool.ts +68 -0
- package/src/engine.ts +4564 -0
- package/src/export-tool.ts +81 -0
- package/src/exporters.ts +399 -0
- package/src/gate.ts +330 -0
- package/src/granularity.ts +312 -0
- package/src/index.ts +289 -0
- package/src/json-value.ts +20 -0
- package/src/model-file.ts +49 -0
- package/src/option-shape.ts +64 -0
- package/src/report-diff-tool.ts +67 -0
- package/src/report-view.ts +483 -0
- package/src/structure-tool.ts +93 -0
- package/src/structure.ts +786 -0
- package/src/tool.ts +103 -0
- package/src/uml-communication.ts +428 -0
- package/src/uml-composite.ts +520 -0
- package/src/uml-interaction-overview.ts +321 -0
- package/src/uml-profile.ts +439 -0
- package/src/uml-structure.ts +317 -0
- package/src/uml-syntax.ts +764 -0
- package/src/uml-timing.ts +1106 -0
- package/src/uml-tool.ts +230 -0
- package/src/uml-usecase.ts +566 -0
- package/src/uml.ts +2139 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The interaction overview diagram module.
|
|
3
|
+
*
|
|
4
|
+
* An interaction overview is a control-flow graph whose nodes are interactions: a start, a
|
|
5
|
+
* decision, one or more references to interactions, and a stop. PlantUML has no page for it
|
|
6
|
+
* and no dedicated type, but the drawing is expressible with the activity diagram's
|
|
7
|
+
* vocabulary plus a reference node - `start`, `:ref Login sequence;`, `if (ok?) then (yes)`
|
|
8
|
+
* - which is what the notation research in this repository recorded as the manual form.
|
|
9
|
+
*
|
|
10
|
+
* So this family is drawn approximately, like the composite structure and the
|
|
11
|
+
* communication diagram, and for the same reason the family table used to refuse it on
|
|
12
|
+
* grounds that were simply wrong ("neither notation spells the inline interaction
|
|
13
|
+
* references": it does).
|
|
14
|
+
*
|
|
15
|
+
* Reading it back is partial, and the parser refuses the part it cannot do. A sequence and
|
|
16
|
+
* an `if`/`else` are spelled clearly enough to reconstruct. A `fork` is not: the notation
|
|
17
|
+
* opens a `fork` block, lists the flows, and closes it with `end fork`, and the closing
|
|
18
|
+
* marker carries no label tying it to the opening one, so nested or adjacent forks cannot
|
|
19
|
+
* be told apart. Rather than guess, a diagram containing one is refused with that reason.
|
|
20
|
+
*
|
|
21
|
+
* @module model-everything-uml-interaction-overview
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
UmlError, directiveLine, type UmlNotation, type UmlFamilyModule,
|
|
26
|
+
type UmlFamilyParse, type UmlFamilyRender, type UmlFamilyFinding, type UmlFamilyRenderOptions,
|
|
27
|
+
} from './uml-syntax.js'
|
|
28
|
+
|
|
29
|
+
/** What a node of the overview is. */
|
|
30
|
+
export type InteractionOverviewNodeKind = 'initial' | 'final' | 'decision' | 'merge' | 'interaction' | 'reference'
|
|
31
|
+
|
|
32
|
+
export interface InteractionOverviewNode {
|
|
33
|
+
id: string
|
|
34
|
+
kind: InteractionOverviewNodeKind
|
|
35
|
+
/** The interaction this node runs, inline (`interaction`) or by reference (`reference`). */
|
|
36
|
+
interaction?: string
|
|
37
|
+
label?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface InteractionOverviewEdge {
|
|
41
|
+
from: string
|
|
42
|
+
to: string
|
|
43
|
+
/** The condition on the branch, as the diagram writes it. */
|
|
44
|
+
condition?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* An interaction overview diagram as a model.
|
|
49
|
+
*
|
|
50
|
+
* The flow is what the diagram is: nodes and the edges between them. `interaction` and
|
|
51
|
+
* `reference` are kept apart because they are different facts - one is the interaction
|
|
52
|
+
* written here, the other is a name that points somewhere else and may not exist.
|
|
53
|
+
*/
|
|
54
|
+
export interface InteractionOverviewModelV1 {
|
|
55
|
+
schemaVersion: 1
|
|
56
|
+
nodes: InteractionOverviewNode[]
|
|
57
|
+
edges: InteractionOverviewEdge[]
|
|
58
|
+
/** Ids an edge named that no node declaration introduced. */
|
|
59
|
+
undeclared?: string[]
|
|
60
|
+
narrative?: { nodes?: Record<string, string> }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const NODE_KINDS: readonly InteractionOverviewNodeKind[] = ['initial', 'final', 'decision', 'merge', 'interaction', 'reference']
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate a candidate model and normalize it.
|
|
67
|
+
*
|
|
68
|
+
* One initial node, and every node reachable from it: an overview whose flow starts nowhere
|
|
69
|
+
* is a drawing, not a flow. That is a refusal rather than a finding because every later
|
|
70
|
+
* check would be reading a graph with no entry.
|
|
71
|
+
*/
|
|
72
|
+
export function validateInteractionOverviewModel(input: unknown): InteractionOverviewModelV1 {
|
|
73
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
74
|
+
throw new UmlError('an interaction overview model must be an object with schemaVersion 1, nodes and edges', 'UML_INPUT')
|
|
75
|
+
}
|
|
76
|
+
const model = input as Partial<InteractionOverviewModelV1>
|
|
77
|
+
if (model.schemaVersion !== 1) throw new UmlError('an interaction overview model must declare schemaVersion 1', 'UML_INPUT')
|
|
78
|
+
for (const [name, value] of Object.entries({ nodes: model.nodes ?? [], edges: model.edges ?? [] })) {
|
|
79
|
+
if (!Array.isArray(value)) throw new UmlError(name + ' must be an array', 'UML_INPUT')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const nodes: InteractionOverviewNode[] = []
|
|
83
|
+
const known = new Set<string>()
|
|
84
|
+
for (const node of model.nodes ?? []) {
|
|
85
|
+
if (node === null || typeof node !== 'object' || typeof node.id !== 'string' || node.id.trim() === '') {
|
|
86
|
+
throw new UmlError('every node needs a non-empty string id', 'UML_INPUT')
|
|
87
|
+
}
|
|
88
|
+
if (known.has(node.id)) throw new UmlError('node ' + JSON.stringify(node.id) + ' is declared more than once', 'UML_INPUT')
|
|
89
|
+
known.add(node.id)
|
|
90
|
+
const kind = node.kind ?? 'interaction'
|
|
91
|
+
if (!NODE_KINDS.includes(kind)) {
|
|
92
|
+
throw new UmlError('node kind must be one of ' + NODE_KINDS.join(' | ') + ', got ' + JSON.stringify(String(kind)), 'UML_INPUT')
|
|
93
|
+
}
|
|
94
|
+
if ((kind === 'interaction' || kind === 'reference') && (typeof node.interaction !== 'string' || node.interaction.trim() === '')) {
|
|
95
|
+
throw new UmlError('a ' + kind + ' node needs a non-empty `interaction` name', 'UML_INPUT')
|
|
96
|
+
}
|
|
97
|
+
nodes.push({
|
|
98
|
+
id: node.id,
|
|
99
|
+
kind,
|
|
100
|
+
...(node.interaction === undefined ? {} : { interaction: node.interaction }),
|
|
101
|
+
...(node.label === undefined ? {} : { label: node.label }),
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const initial = nodes.filter((node) => node.kind === 'initial')
|
|
106
|
+
if (initial.length !== 1) {
|
|
107
|
+
throw new UmlError('an interaction overview needs exactly one initial node, got ' + String(initial.length), 'UML_INPUT')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const undeclared = new Set<string>()
|
|
111
|
+
const edges: InteractionOverviewEdge[] = []
|
|
112
|
+
for (const edge of model.edges ?? []) {
|
|
113
|
+
if (edge === null || typeof edge !== 'object') throw new UmlError('every edge must be an object', 'UML_INPUT')
|
|
114
|
+
for (const side of ['from', 'to'] as const) {
|
|
115
|
+
const endpoint = edge[side]
|
|
116
|
+
if (typeof endpoint !== 'string' || endpoint.trim() === '') throw new UmlError('every edge needs a non-empty `' + side + '` end', 'UML_INPUT')
|
|
117
|
+
if (!known.has(endpoint)) undeclared.add(endpoint)
|
|
118
|
+
}
|
|
119
|
+
edges.push({
|
|
120
|
+
from: edge.from,
|
|
121
|
+
to: edge.to,
|
|
122
|
+
...(edge.condition === undefined ? {} : { condition: edge.condition }),
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
nodes,
|
|
129
|
+
edges,
|
|
130
|
+
...(undeclared.size === 0 ? {} : { undeclared: [...undeclared].sort() }),
|
|
131
|
+
...(model.narrative === undefined ? {} : { narrative: model.narrative }),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function quote(text: string): string {
|
|
136
|
+
return '"' + text.replace(/"/g, "'") + '"'
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Rendering
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Render an interaction overview as Mermaid.
|
|
145
|
+
*
|
|
146
|
+
* Mermaid is the notation this family is drawn in, because the view is an activity graph
|
|
147
|
+
* and PlantUML's activity syntax is refused by this build (a structured flowchart language
|
|
148
|
+
* cannot carry a graph with merges or cycles without a while/if reconstruction). A Mermaid
|
|
149
|
+
* flowchart carries exactly what an overview is: nodes, labelled branches, and a start and
|
|
150
|
+
* an end.
|
|
151
|
+
*
|
|
152
|
+
* @param input - candidate `InteractionOverviewModelV1`.
|
|
153
|
+
* @param notation - only `mermaid` draws this family.
|
|
154
|
+
*/
|
|
155
|
+
export function renderInteractionOverview(input: unknown, notation: UmlNotation): UmlFamilyRender {
|
|
156
|
+
if (notation !== 'mermaid') {
|
|
157
|
+
throw new UmlError('an interaction overview can only be drawn in mermaid; plantuml has no interaction reference node, and its activity syntax - the only place a `ref` step exists - is refused by this build because a structured flowchart language cannot carry a graph with merges or cycles. Render it with notation "mermaid".', 'UML_VIEW_UNREPRESENTABLE')
|
|
158
|
+
}
|
|
159
|
+
const model = validateInteractionOverviewModel(input)
|
|
160
|
+
const warnings: string[] = [
|
|
161
|
+
'UML_RENDER_APPROXIMATE_VIEW: mermaid has no interaction overview diagram; this is a flowchart whose nodes name interactions. The flow, the references and the branch conditions are carried; the combined fragments of an inline interaction are not drawn, because the drawing has one node for it.',
|
|
162
|
+
]
|
|
163
|
+
const lines: string[] = ['flowchart TD']
|
|
164
|
+
lines.push(directiveLine('mermaid', 'uml v1 notation=mermaid family=interaction-overview'))
|
|
165
|
+
const alias = (id: string): string => id.replace(/[^A-Za-z0-9_]/g, '_')
|
|
166
|
+
for (const node of model.nodes) {
|
|
167
|
+
const a = alias(node.id)
|
|
168
|
+
if (node.kind === 'initial') { lines.push(' ' + a + '(["start"])'); continue }
|
|
169
|
+
if (node.kind === 'final') { lines.push(' ' + a + '(["stop"])'); continue }
|
|
170
|
+
if (node.kind === 'decision') { lines.push(' ' + a + '{"' + (node.label ?? 'branch?').replace(/"/g, "'") + '"}'); continue }
|
|
171
|
+
if (node.kind === 'merge') { lines.push(' ' + a + '["merge"]'); continue }
|
|
172
|
+
const name = (node.interaction ?? node.label ?? node.id).replace(/"/g, "'")
|
|
173
|
+
lines.push(' ' + a + (node.kind === 'reference' ? '[/"' + name + '"/]' : '["' + name + '"]'))
|
|
174
|
+
}
|
|
175
|
+
for (const edge of model.edges) {
|
|
176
|
+
const label = edge.condition === undefined || edge.condition === '' ? '' : '|"' + edge.condition.replace(/[|"]/g, "'") + '"|'
|
|
177
|
+
lines.push(' ' + alias(edge.from) + ' -->' + label + ' ' + alias(edge.to))
|
|
178
|
+
}
|
|
179
|
+
return { primary: lines.join('\n') + '\n', warnings }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Parsing
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Review
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
export function parseInteractionOverview(_text: string, _notation: UmlNotation): UmlFamilyParse<InteractionOverviewModelV1> {
|
|
190
|
+
throw new UmlError(
|
|
191
|
+
'an interaction overview is drawn here as a mermaid flowchart, and a flowchart node carries a name rather than the interaction it stands for: reading one back would have to guess whether a node is a reference to another diagram, an inline interaction, or a plain step. Keep the overview as its own model and review that.',
|
|
192
|
+
'UML_VIEW_UNREPRESENTABLE')
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Review
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
export type InteractionOverviewFindingCode =
|
|
200
|
+
| 'UML069_OVERVIEW_NO_INITIAL'
|
|
201
|
+
| 'UML070_OVERVIEW_NODE_UNREACHABLE'
|
|
202
|
+
| 'UML071_OVERVIEW_DECISION_ONE_BRANCH'
|
|
203
|
+
| 'UML072_OVERVIEW_REFERENCE_UNDECLARED'
|
|
204
|
+
| 'UML073_OVERVIEW_NODE_WITHOUT_INTERACTION'
|
|
205
|
+
| 'UML074_OVERVIEW_UNDOCUMENTED'
|
|
206
|
+
|
|
207
|
+
export const INTERACTION_OVERVIEW_FINDING_CODES: readonly InteractionOverviewFindingCode[] = [
|
|
208
|
+
'UML069_OVERVIEW_NO_INITIAL',
|
|
209
|
+
'UML070_OVERVIEW_NODE_UNREACHABLE',
|
|
210
|
+
'UML071_OVERVIEW_DECISION_ONE_BRANCH',
|
|
211
|
+
'UML072_OVERVIEW_REFERENCE_UNDECLARED',
|
|
212
|
+
'UML073_OVERVIEW_NODE_WITHOUT_INTERACTION',
|
|
213
|
+
'UML074_OVERVIEW_UNDOCUMENTED',
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* What a reader would get wrong about an interaction overview.
|
|
218
|
+
*
|
|
219
|
+
* The overview's job is to say how the interactions fit together, so the checks are about
|
|
220
|
+
* the flow being a flow: one way in, every node on some path from it, a decision that
|
|
221
|
+
* actually decides. Whether the referenced interactions exist is a question about the other
|
|
222
|
+
* diagrams - this plugin holds no registry of them - so an undeclared reference is reported
|
|
223
|
+
* as a name nothing here can resolve, not as a missing interaction.
|
|
224
|
+
*/
|
|
225
|
+
export function reviewInteractionOverviewModel(model: InteractionOverviewModelV1, labels: Record<string, string>): UmlFamilyFinding[] {
|
|
226
|
+
const findings: UmlFamilyFinding[] = []
|
|
227
|
+
const name = (id: string): string => labels[id] ?? id
|
|
228
|
+
const byId = new Map(model.nodes.map((node) => [node.id, node]))
|
|
229
|
+
|
|
230
|
+
const initial = model.nodes.filter((node) => node.kind === 'initial')
|
|
231
|
+
if (initial.length === 0) {
|
|
232
|
+
findings.push({
|
|
233
|
+
code: 'UML069_OVERVIEW_NO_INITIAL',
|
|
234
|
+
severity: 'error',
|
|
235
|
+
message: 'the overview has no initial node, so the flow has no entry and nothing below it can be read as a sequence.',
|
|
236
|
+
evidence: { initial: 0 },
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const outgoing = new Map<string, string[]>()
|
|
241
|
+
for (const edge of model.edges) {
|
|
242
|
+
const list = outgoing.get(edge.from)
|
|
243
|
+
if (list === undefined) outgoing.set(edge.from, [edge.to])
|
|
244
|
+
else list.push(edge.to)
|
|
245
|
+
}
|
|
246
|
+
const reached = new Set<string>()
|
|
247
|
+
const queue = initial.map((node) => node.id)
|
|
248
|
+
while (queue.length > 0) {
|
|
249
|
+
const id = queue.shift() as string
|
|
250
|
+
if (reached.has(id)) continue
|
|
251
|
+
reached.add(id)
|
|
252
|
+
for (const next of outgoing.get(id) ?? []) queue.push(next)
|
|
253
|
+
}
|
|
254
|
+
for (const node of model.nodes) {
|
|
255
|
+
if (reached.has(node.id)) continue
|
|
256
|
+
findings.push({
|
|
257
|
+
code: 'UML070_OVERVIEW_NODE_UNREACHABLE',
|
|
258
|
+
severity: 'error',
|
|
259
|
+
message: 'node ' + name(node.id) + ' is on no path from the initial node, so the overview does not run it.',
|
|
260
|
+
evidence: { id: node.id, kind: node.kind },
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const node of model.nodes) {
|
|
265
|
+
if (node.kind !== 'decision') continue
|
|
266
|
+
const branches = outgoing.get(node.id) ?? []
|
|
267
|
+
if (branches.length > 1) continue
|
|
268
|
+
findings.push({
|
|
269
|
+
code: 'UML071_OVERVIEW_DECISION_ONE_BRANCH',
|
|
270
|
+
severity: 'warning',
|
|
271
|
+
message: 'decision ' + name(node.id) + ' has ' + String(branches.length) + ' outgoing edge(s), so it does not decide anything.',
|
|
272
|
+
evidence: { id: node.id, branches: branches.length },
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const undeclared = model.undeclared ?? []
|
|
277
|
+
if (undeclared.length > 0) {
|
|
278
|
+
findings.push({
|
|
279
|
+
code: 'UML072_OVERVIEW_REFERENCE_UNDECLARED',
|
|
280
|
+
severity: 'error',
|
|
281
|
+
message: 'an edge names ' + String(undeclared.length) + ' node(s) the overview never declares. A reference whose target lives in another diagram cannot be resolved here at all.',
|
|
282
|
+
detail: undeclared.join(', '),
|
|
283
|
+
evidence: { ids: undeclared },
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (const node of model.nodes) {
|
|
288
|
+
if (node.kind !== 'interaction' && node.kind !== 'reference') continue
|
|
289
|
+
if (typeof node.interaction === 'string' && node.interaction.trim() !== '') continue
|
|
290
|
+
findings.push({
|
|
291
|
+
code: 'UML073_OVERVIEW_NODE_WITHOUT_INTERACTION',
|
|
292
|
+
severity: 'error',
|
|
293
|
+
message: 'node ' + name(node.id) + ' is declared as an ' + node.kind + ' but names no interaction, so the overview does not say what runs there.',
|
|
294
|
+
evidence: { id: node.id, kind: node.kind },
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (model.narrative?.nodes === undefined) {
|
|
299
|
+
for (const node of model.nodes) {
|
|
300
|
+
if (node.label !== undefined || node.interaction !== undefined || labels[node.id] !== undefined) continue
|
|
301
|
+
findings.push({
|
|
302
|
+
code: 'UML074_OVERVIEW_UNDOCUMENTED',
|
|
303
|
+
severity: 'info',
|
|
304
|
+
message: 'node ' + node.id + ' carries no name a reader can map back to the design: give it a label, an interaction or a narrative entry.',
|
|
305
|
+
evidence: { id: node.id },
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return findings
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** The interaction overview family, as the engine's registry expects it. */
|
|
313
|
+
export const interactionOverviewModule: UmlFamilyModule<InteractionOverviewModelV1> = {
|
|
314
|
+
id: 'interaction-overview',
|
|
315
|
+
// Mermaid is the only notation this family is drawn in: PlantUML's activity syntax, the
|
|
316
|
+
// one place a `ref` step exists, is refused by this build.
|
|
317
|
+
notation: 'mermaid',
|
|
318
|
+
render: (input: unknown, notation: UmlNotation, _options: UmlFamilyRenderOptions): UmlFamilyRender => renderInteractionOverview(input, notation),
|
|
319
|
+
parse: (text: string, notation: UmlNotation): UmlFamilyParse<InteractionOverviewModelV1> => parseInteractionOverview(text, notation),
|
|
320
|
+
review: (model: InteractionOverviewModelV1, labels: Record<string, string>): UmlFamilyFinding[] => reviewInteractionOverviewModel(model, labels),
|
|
321
|
+
}
|