@memoarchitect/tools 0.4.6 → 0.6.1

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 (121) hide show
  1. package/README.md +8 -5
  2. package/package.json +3 -3
  3. package/packages/tools/lib/bin/memo.js +3 -5
  4. package/packages/tools/lib/bin/memo.js.map +1 -1
  5. package/packages/tools/lib/browser/index.d.ts +2 -0
  6. package/packages/tools/lib/browser/index.d.ts.map +1 -1
  7. package/packages/tools/lib/browser/index.js +2 -0
  8. package/packages/tools/lib/browser/index.js.map +1 -1
  9. package/packages/tools/lib/commands/create-package.js +1 -1
  10. package/packages/tools/lib/commands/dev.d.ts.map +1 -1
  11. package/packages/tools/lib/commands/dev.js +40 -5
  12. package/packages/tools/lib/commands/dev.js.map +1 -1
  13. package/packages/tools/lib/commands/init.d.ts +7 -4
  14. package/packages/tools/lib/commands/init.d.ts.map +1 -1
  15. package/packages/tools/lib/commands/init.js +30 -37
  16. package/packages/tools/lib/commands/init.js.map +1 -1
  17. package/packages/tools/lib/commands/install.js +1 -1
  18. package/packages/tools/lib/commands/ontology.js +1 -1
  19. package/packages/tools/lib/commands/ontology.js.map +1 -1
  20. package/packages/tools/lib/dhf/document-registry.js +5 -5
  21. package/packages/tools/lib/dhf/document-registry.js.map +1 -1
  22. package/packages/tools/lib/dhf/template-engine.js +3 -3
  23. package/packages/tools/lib/dhf/template-engine.js.map +1 -1
  24. package/packages/tools/lib/importer/cameo-importer.d.ts.map +1 -1
  25. package/packages/tools/lib/importer/cameo-importer.js +5 -2
  26. package/packages/tools/lib/importer/cameo-importer.js.map +1 -1
  27. package/packages/tools/lib/importer/ea-importer.d.ts +1 -1
  28. package/packages/tools/lib/importer/ea-importer.d.ts.map +1 -1
  29. package/packages/tools/lib/importer/ea-importer.js +6 -4
  30. package/packages/tools/lib/importer/ea-importer.js.map +1 -1
  31. package/packages/tools/lib/index.d.ts +2 -0
  32. package/packages/tools/lib/index.d.ts.map +1 -1
  33. package/packages/tools/lib/index.js +2 -0
  34. package/packages/tools/lib/index.js.map +1 -1
  35. package/packages/tools/lib/language/generated/ast.d.ts +409 -42
  36. package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
  37. package/packages/tools/lib/language/generated/ast.js +318 -12
  38. package/packages/tools/lib/language/generated/ast.js.map +1 -1
  39. package/packages/tools/lib/language/generated/grammar.d.ts.map +1 -1
  40. package/packages/tools/lib/language/generated/grammar.js +1732 -513
  41. package/packages/tools/lib/language/generated/grammar.js.map +1 -1
  42. package/packages/tools/lib/model/builder.d.ts.map +1 -1
  43. package/packages/tools/lib/model/builder.js +38 -14
  44. package/packages/tools/lib/model/builder.js.map +1 -1
  45. package/packages/tools/lib/model/config.d.ts +11 -1
  46. package/packages/tools/lib/model/config.d.ts.map +1 -1
  47. package/packages/tools/lib/model/kind-registry.d.ts +11 -0
  48. package/packages/tools/lib/model/kind-registry.d.ts.map +1 -1
  49. package/packages/tools/lib/model/kind-registry.js +33 -7
  50. package/packages/tools/lib/model/kind-registry.js.map +1 -1
  51. package/packages/tools/lib/model/layer-resolver.d.ts +10 -0
  52. package/packages/tools/lib/model/layer-resolver.d.ts.map +1 -1
  53. package/packages/tools/lib/model/layer-resolver.js +49 -0
  54. package/packages/tools/lib/model/layer-resolver.js.map +1 -1
  55. package/packages/tools/lib/model/manifest.d.ts +2 -2
  56. package/packages/tools/lib/model/manifest.d.ts.map +1 -1
  57. package/packages/tools/lib/model/manifest.js +3 -2
  58. package/packages/tools/lib/model/manifest.js.map +1 -1
  59. package/packages/tools/lib/model/ontology-loader.d.ts +2 -0
  60. package/packages/tools/lib/model/ontology-loader.d.ts.map +1 -1
  61. package/packages/tools/lib/model/ontology-loader.js +92 -28
  62. package/packages/tools/lib/model/ontology-loader.js.map +1 -1
  63. package/packages/tools/lib/model/parser-utils.d.ts +9 -0
  64. package/packages/tools/lib/model/parser-utils.d.ts.map +1 -1
  65. package/packages/tools/lib/model/parser-utils.js +24 -0
  66. package/packages/tools/lib/model/parser-utils.js.map +1 -1
  67. package/packages/tools/lib/model/paths.d.ts +0 -2
  68. package/packages/tools/lib/model/paths.d.ts.map +1 -1
  69. package/packages/tools/lib/model/paths.js +0 -2
  70. package/packages/tools/lib/model/paths.js.map +1 -1
  71. package/packages/tools/lib/model/relationship-legality.d.ts +266 -0
  72. package/packages/tools/lib/model/relationship-legality.d.ts.map +1 -0
  73. package/packages/tools/lib/model/relationship-legality.js +391 -0
  74. package/packages/tools/lib/model/relationship-legality.js.map +1 -0
  75. package/packages/tools/lib/model/relationship-registry.d.ts +38 -4
  76. package/packages/tools/lib/model/relationship-registry.d.ts.map +1 -1
  77. package/packages/tools/lib/model/relationship-registry.js +109 -1
  78. package/packages/tools/lib/model/relationship-registry.js.map +1 -1
  79. package/packages/tools/lib/model/semantic.d.ts +42 -0
  80. package/packages/tools/lib/model/semantic.d.ts.map +1 -1
  81. package/packages/tools/lib/model/semantic.js +3 -0
  82. package/packages/tools/lib/model/semantic.js.map +1 -1
  83. package/packages/tools/lib/model/short-id.js +1 -1
  84. package/packages/tools/lib/model/short-id.js.map +1 -1
  85. package/packages/tools/lib/model/source-affinity.d.ts +21 -0
  86. package/packages/tools/lib/model/source-affinity.d.ts.map +1 -0
  87. package/packages/tools/lib/model/source-affinity.js +42 -0
  88. package/packages/tools/lib/model/source-affinity.js.map +1 -0
  89. package/packages/tools/lib/model/source-graph.d.ts +30 -0
  90. package/packages/tools/lib/model/source-graph.d.ts.map +1 -0
  91. package/packages/tools/lib/model/source-graph.js +154 -0
  92. package/packages/tools/lib/model/source-graph.js.map +1 -0
  93. package/packages/tools/lib/model/view-kinds.d.ts.map +1 -1
  94. package/packages/tools/lib/model/view-kinds.js +1 -0
  95. package/packages/tools/lib/model/view-kinds.js.map +1 -1
  96. package/packages/tools/lib/operations/project-snapshot.d.ts.map +1 -1
  97. package/packages/tools/lib/operations/project-snapshot.js +2 -15
  98. package/packages/tools/lib/operations/project-snapshot.js.map +1 -1
  99. package/packages/tools/lib/protocol/messages.d.ts +92 -8
  100. package/packages/tools/lib/protocol/messages.d.ts.map +1 -1
  101. package/packages/tools/lib/server/dev-server.d.ts +16 -0
  102. package/packages/tools/lib/server/dev-server.d.ts.map +1 -1
  103. package/packages/tools/lib/server/dev-server.js +190 -16
  104. package/packages/tools/lib/server/dev-server.js.map +1 -1
  105. package/packages/tools/lib/server/file-watcher.d.ts +21 -2
  106. package/packages/tools/lib/server/file-watcher.d.ts.map +1 -1
  107. package/packages/tools/lib/server/file-watcher.js +55 -23
  108. package/packages/tools/lib/server/file-watcher.js.map +1 -1
  109. package/packages/tools/lib/server/persistor.d.ts +0 -13
  110. package/packages/tools/lib/server/persistor.d.ts.map +1 -1
  111. package/packages/tools/lib/server/persistor.js +3 -31
  112. package/packages/tools/lib/server/persistor.js.map +1 -1
  113. package/packages/tools/lib/server/relationship-writer.d.ts +104 -0
  114. package/packages/tools/lib/server/relationship-writer.d.ts.map +1 -0
  115. package/packages/tools/lib/server/relationship-writer.js +402 -0
  116. package/packages/tools/lib/server/relationship-writer.js.map +1 -0
  117. package/packages/tools/lib/server/view-layout-store.d.ts +14 -1
  118. package/packages/tools/lib/server/view-layout-store.d.ts.map +1 -1
  119. package/packages/tools/lib/server/view-layout-store.js +63 -11
  120. package/packages/tools/lib/server/view-layout-store.js.map +1 -1
  121. package/packages/tools/src/grammar/memo-sysml.langium +102 -18
@@ -0,0 +1,266 @@
1
+ import type { MemoElement, MemoModelDTO, MemoRelationship } from './semantic.js';
2
+ /** Declared multiplicity on a connection end, e.g. `[0..1]`, `[1]`, `[*]`. */
3
+ export interface MultiplicityDTO {
4
+ /** Lower bound (0 when unbounded-only, e.g. `[*]`). */
5
+ lower: number;
6
+ /** Upper bound; null means unbounded (`*`). */
7
+ upper: number | null;
8
+ }
9
+ /** One end of a relationship definition. */
10
+ export interface RelationshipEndDTO {
11
+ /** End name as declared in SysML, e.g. "control", "requiredElement". */
12
+ name: string;
13
+ /** Declared end type (a kind name). Undefined ends accept any kind. */
14
+ type?: string;
15
+ /** Declared multiplicity, when the ontology states one. */
16
+ multiplicity?: MultiplicityDTO;
17
+ }
18
+ /**
19
+ * Serializable projection of one RelationshipRegistry entry — the wire format
20
+ * the web client uses to reason about legality without a parser.
21
+ */
22
+ export interface RelationshipDefinitionDTO {
23
+ /** Normalized camelCase name used on MemoRelationship.type, e.g. "satisfiedBy". */
24
+ name: string;
25
+ /** PascalCase SysML name of the `connection def`, e.g. "SatisfiedBy". */
26
+ sysmlName: string;
27
+ /** Human-readable label, e.g. "Satisfied By". */
28
+ label: string;
29
+ /** Doc comment from the connection definition, when present. */
30
+ description?: string;
31
+ /** Architecture layer the connection definition lives in. */
32
+ layer: string;
33
+ /** Abstract definitions are not directly instantiable. */
34
+ isAbstract?: boolean;
35
+ /**
36
+ * Whether an element may be related to itself by this relation, from the
37
+ * ontology's `isReflexive` attribute. Undefined means the ontology is
38
+ * silent, and self-links are refused — see SELF_LINK_ALLOWED_BY_DEFAULT.
39
+ */
40
+ isReflexive?: boolean;
41
+ /**
42
+ * Whether the same ordered pair may carry more than one link of this type,
43
+ * from the ontology's `isUnique` attribute. Undefined means the ontology is
44
+ * silent, and repeats are refused — see DUPLICATES_ALLOWED_BY_DEFAULT.
45
+ */
46
+ isUnique?: boolean;
47
+ /** First declared end — the source/from end. */
48
+ sourceEnd: RelationshipEndDTO;
49
+ /** Second declared end — the target/to end. */
50
+ targetEnd: RelationshipEndDTO;
51
+ }
52
+ /** Serializable projection of one KindRegistry entry. */
53
+ export interface KindDefinitionDTO {
54
+ name: string;
55
+ label: string;
56
+ layer: string;
57
+ /** SysML v2 construct, e.g. "part def". */
58
+ construct: string;
59
+ /** Direct supertype, the basis for transitive conformance. */
60
+ superType?: string;
61
+ isAbstract?: boolean;
62
+ /**
63
+ * Ontology namespace segments derived from the source tree.
64
+ * Example: ["assurance", "safety_risk", "analysis"].
65
+ */
66
+ namespace?: string[];
67
+ }
68
+ /** Both ontology registries in serializable form, as shipped to the client. */
69
+ export interface OntologyRegistriesDTO {
70
+ relationships: RelationshipDefinitionDTO[];
71
+ kinds: KindDefinitionDTO[];
72
+ }
73
+ /** Direction of a relationship relative to the selected element. */
74
+ export type RelationshipDirection = 'outgoing' | 'incoming';
75
+ /** A request to create one model relationship. */
76
+ export interface RelationshipCreateRequest {
77
+ /** Correlates the response with the pending UI row. */
78
+ requestId: string;
79
+ /** Relationship type — camelCase name or PascalCase sysmlName. */
80
+ type: string;
81
+ /** Element occupying the relationship's source end. */
82
+ sourceId: string;
83
+ /** Element occupying the relationship's target end. */
84
+ targetId: string;
85
+ /** Direction the user chose, relative to the element they had selected. */
86
+ direction: RelationshipDirection;
87
+ /** Element the user had selected when they opened the dialog. */
88
+ selectedElementId?: string;
89
+ /** Requested owning package or project-relative file, when the user picked one. */
90
+ owningFile?: string;
91
+ /** Diagram the request was invoked from, when invoked from a diagram. */
92
+ diagramId?: string;
93
+ }
94
+ /** A request to delete one model relationship. */
95
+ export interface RelationshipDeleteRequest {
96
+ requestId: string;
97
+ /** Stable relationship ID (the connection usage name). */
98
+ relationshipId: string;
99
+ /** Source file the caller believes owns it — checked against the model. */
100
+ sourceFile?: string;
101
+ }
102
+ /** Machine-readable codes so callers can branch without matching on prose. */
103
+ export type RelationshipDiagnosticCode = 'REL-001' | 'REL-002' | 'REL-003' | 'REL-004' | 'REL-005' | 'REL-006' | 'REL-007' | 'REL-008' | 'REL-009' | 'REL-010';
104
+ /** One actionable validation failure. */
105
+ export interface RelationshipDiagnostic {
106
+ code: RelationshipDiagnosticCode;
107
+ message: string;
108
+ /** 'error' blocks the mutation; 'warning' is advisory (view-profile fit). */
109
+ severity: 'error' | 'warning';
110
+ }
111
+ /** Outcome of validating a mutation request. */
112
+ export interface RelationshipValidationResult {
113
+ /** True when no error-severity diagnostic was raised. */
114
+ valid: boolean;
115
+ diagnostics: RelationshipDiagnostic[];
116
+ /** Normalized camelCase relationship type, when the type resolved. */
117
+ normalizedType?: string;
118
+ /** The resolved definition, when the type resolved. */
119
+ definition?: RelationshipDefinitionDTO;
120
+ }
121
+ /** How an undeclared `isReflexive` is read. */
122
+ export declare const SELF_LINK_ALLOWED_BY_DEFAULT = false;
123
+ /** How an undeclared `isUnique` is read (unique ⇒ duplicates refused). */
124
+ export declare const DUPLICATES_ALLOWED_BY_DEFAULT = false;
125
+ /** Whether this relation permits an element to be related to itself. */
126
+ export declare function allowsSelfLink(definition: RelationshipDefinitionDTO): boolean;
127
+ /** Whether this relation permits the same ordered pair to be linked twice. */
128
+ export declare function allowsDuplicates(definition: RelationshipDefinitionDTO): boolean;
129
+ /**
130
+ * How constrained a relation is, as the number of ends it types (0–2).
131
+ *
132
+ * Read off the ontology rather than a curated list: a relation that types both
133
+ * ends says the most about what it connects, and one that types neither can
134
+ * join anything. Callers order by this so precise relations are offered ahead
135
+ * of general ones.
136
+ */
137
+ export declare function relationshipSpecificity(definition: RelationshipDefinitionDTO): number;
138
+ /**
139
+ * True for a relation that can join any two elements because it types neither
140
+ * end — MemoLink and anything else declared the same way. These are the
141
+ * fallback when no specific relation carries the meaning, so they are offered
142
+ * last rather than suppressed.
143
+ */
144
+ export declare function isUniversalRelationship(definition: RelationshipDefinitionDTO): boolean;
145
+ /** Index kind definitions by name for O(1) supertype walks. */
146
+ export declare function indexKinds(kinds: KindDefinitionDTO[]): Map<string, KindDefinitionDTO>;
147
+ /**
148
+ * Resolve a relationship definition by camelCase name or PascalCase sysmlName.
149
+ * Callers may hold either form (the model uses camelCase, SysML source uses
150
+ * PascalCase), so both resolve to the same definition.
151
+ */
152
+ export declare function findRelationshipDefinition(type: string, registries: OntologyRegistriesDTO): RelationshipDefinitionDTO | undefined;
153
+ /**
154
+ * True when `actualKind` may occupy an end declared as `expectedKind`.
155
+ *
156
+ * Conformance is transitive over the kind specialization chain, so
157
+ * HardwareAssembly :> ArchitectureElement :> MemoPart conforms to all three.
158
+ * An undefined or empty expected type accepts any kind (untyped end).
159
+ */
160
+ export declare function kindConformsTo(actualKind: string, expectedKind: string | undefined, kindRegistry: Map<string, KindDefinitionDTO> | KindDefinitionDTO[]): boolean;
161
+ /**
162
+ * Which ends of `definition` the element may occupy.
163
+ *
164
+ * 'outgoing' means the element can sit on the source end (element → other);
165
+ * 'incoming' means it can sit on the target end (other → element). An element
166
+ * whose kind conforms to both ends legally supports both directions.
167
+ */
168
+ export declare function legalRelationshipDirections(selectedElement: Pick<MemoElement, 'kind'>, definition: RelationshipDefinitionDTO, registries: OntologyRegistriesDTO): RelationshipDirection[];
169
+ /**
170
+ * Every relationship type the element can participate in, with the directions
171
+ * it may take. This is what the Properties panel lists under Add Relationship
172
+ * before the user has picked an opposite endpoint.
173
+ */
174
+ export declare function legalRelationshipsForElement(selectedElement: Pick<MemoElement, 'kind'>, registries: OntologyRegistriesDTO): Array<{
175
+ definition: RelationshipDefinitionDTO;
176
+ directions: RelationshipDirection[];
177
+ }>;
178
+ /** One legal (type, direction) pair between two concrete elements. */
179
+ export interface LegalRelationshipOption {
180
+ definition: RelationshipDefinitionDTO;
181
+ /** Direction relative to `sourceElement` — the first argument. */
182
+ direction: RelationshipDirection;
183
+ /** Element that ends up on the source end for this option. */
184
+ sourceId: string;
185
+ /** Element that ends up on the target end for this option. */
186
+ targetId: string;
187
+ }
188
+ /**
189
+ * All relationship types legal between two specific elements, in both
190
+ * directions. Backs the target-first workflow: pick any element, then see only
191
+ * the links that could actually connect the two.
192
+ *
193
+ * `direction` is stated relative to `sourceElement`, so an 'incoming' option
194
+ * means sourceElement occupies the target end.
195
+ */
196
+ export declare function legalRelationshipTypes(sourceElement: Pick<MemoElement, 'id' | 'kind'>, targetElement: Pick<MemoElement, 'id' | 'kind'>, registries: OntologyRegistriesDTO): LegalRelationshipOption[];
197
+ /** Filters applied to the opposite-endpoint search. */
198
+ export interface TargetFilter {
199
+ /** Free text matched against element name, id and shortId. */
200
+ query?: string;
201
+ kind?: string;
202
+ layer?: string;
203
+ package?: string;
204
+ }
205
+ /** One candidate for the opposite endpoint, carrying its grouping keys. */
206
+ export interface RelationshipTargetCandidate {
207
+ element: MemoElement;
208
+ kind: string;
209
+ layer: string;
210
+ package?: string;
211
+ }
212
+ /**
213
+ * Every element in the loaded model that may legally occupy the opposite end.
214
+ *
215
+ * Searches the complete model — not only what the active diagram shows — and
216
+ * excludes the selected element itself, since self-relationships are prohibited.
217
+ */
218
+ export declare function compatibleRelationshipTargets(selectedElement: Pick<MemoElement, 'id' | 'kind'>, definition: RelationshipDefinitionDTO, direction: RelationshipDirection, model: Pick<MemoModelDTO, 'elements'>, registries: OntologyRegistriesDTO, filter?: TargetFilter): RelationshipTargetCandidate[];
219
+ /** Group candidates by kind within layer, for the picker's section headers. */
220
+ export declare function groupTargetsByKindAndLayer(candidates: RelationshipTargetCandidate[]): Array<{
221
+ layer: string;
222
+ kind: string;
223
+ elements: MemoElement[];
224
+ }>;
225
+ /** View profile the relationship would have to satisfy to be drawn. */
226
+ export interface ViewProfileContext {
227
+ diagramId: string;
228
+ /** Relationship types the view permits. Empty/undefined means unrestricted. */
229
+ permittedRelationshipTypes?: string[];
230
+ /** Elements the view selects. Empty/undefined means unrestricted. */
231
+ elementIds?: string[];
232
+ }
233
+ /** Everything validation needs beyond the model and registries. */
234
+ export interface RelationshipMutationContext {
235
+ /** Profile of the diagram the request came from, when there was one. */
236
+ viewProfile?: ViewProfileContext;
237
+ /** False when the project has no writable package able to own the link. */
238
+ hasWritableOwner?: boolean;
239
+ }
240
+ /**
241
+ * The complete legality check for a create request. The client runs this to
242
+ * shape the UI; the server runs the identical function again before touching a
243
+ * file, so a stale or hand-crafted request cannot bypass the ontology.
244
+ */
245
+ export declare function validateRelationshipMutation(request: RelationshipCreateRequest, model: Pick<MemoModelDTO, 'elements' | 'relationships'>, registries: OntologyRegistriesDTO, context?: RelationshipMutationContext): RelationshipValidationResult;
246
+ /** Validate a delete request against the loaded model. */
247
+ export declare function validateRelationshipDeletion(request: RelationshipDeleteRequest, model: Pick<MemoModelDTO, 'relationships'>): {
248
+ valid: boolean;
249
+ diagnostics: RelationshipDiagnostic[];
250
+ relationship?: MemoRelationship;
251
+ };
252
+ /**
253
+ * Plain-language preview of a pending relationship, e.g.
254
+ * "Infusion Controller — satisfies → Software Requirement SR-104".
255
+ * Shared so the Properties panel and the diagram picker word it identically.
256
+ */
257
+ export declare function describeRelationship(definition: RelationshipDefinitionDTO, source: Pick<MemoElement, 'name' | 'kind'> | undefined, target: Pick<MemoElement, 'name' | 'kind'> | undefined, options?: {
258
+ sourceId?: string;
259
+ targetId?: string;
260
+ }): string;
261
+ /**
262
+ * Whether an existing relationship should be drawn in a given view: both
263
+ * endpoints selected by the view, and the type permitted by its profile.
264
+ */
265
+ export declare function isRelationshipVisibleInView(relationship: Pick<MemoRelationship, 'type' | 'sourceId' | 'targetId'>, profile: ViewProfileContext): boolean;
266
+ //# sourceMappingURL=relationship-legality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relationship-legality.d.ts","sourceRoot":"","sources":["../../src/model/relationship-legality.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,391 @@
1
+ // ─── Relationship Legality ────────────────────────────────────────────────────
2
+ //
3
+ // The single semantic authority for "may this relationship exist?", shared by
4
+ // the web client (Properties panel, diagram RelationshipPicker, canvas edge
5
+ // creation), the server persistence path, importers, and validation.
6
+ //
7
+ // Everything here is pure and dependency-free — no filesystem, no parser, no
8
+ // Langium AST — so the browser entrypoint can re-export it unchanged. Legality
9
+ // is derived from the ontology registries (RelationshipRegistry / KindRegistry),
10
+ // never from hardcoded relationship tables.
11
+ //
12
+ // Usage:
13
+ // const dirs = legalRelationshipDirections(element, relDef, registries);
14
+ // const targets = compatibleRelationshipTargets(element, relDef, 'outgoing', model, registries);
15
+ // const result = validateRelationshipMutation(request, model, registries);
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+ // ─── Structural properties ──────────────────────────────────────────────────
18
+ //
19
+ // Read from the ontology, with a documented reading when it stays silent. The
20
+ // defaults are deliberately the restrictive ones: a relation that has not been
21
+ // characterized should not silently permit self-links or repeats. Both are
22
+ // stated once here rather than assumed at each call site.
23
+ /** How an undeclared `isReflexive` is read. */
24
+ export const SELF_LINK_ALLOWED_BY_DEFAULT = false;
25
+ /** How an undeclared `isUnique` is read (unique ⇒ duplicates refused). */
26
+ export const DUPLICATES_ALLOWED_BY_DEFAULT = false;
27
+ /** Whether this relation permits an element to be related to itself. */
28
+ export function allowsSelfLink(definition) {
29
+ return definition.isReflexive ?? SELF_LINK_ALLOWED_BY_DEFAULT;
30
+ }
31
+ /** Whether this relation permits the same ordered pair to be linked twice. */
32
+ export function allowsDuplicates(definition) {
33
+ // isUnique is stated positively in the ontology; duplicates are its inverse.
34
+ if (definition.isUnique === undefined)
35
+ return DUPLICATES_ALLOWED_BY_DEFAULT;
36
+ return !definition.isUnique;
37
+ }
38
+ /**
39
+ * How constrained a relation is, as the number of ends it types (0–2).
40
+ *
41
+ * Read off the ontology rather than a curated list: a relation that types both
42
+ * ends says the most about what it connects, and one that types neither can
43
+ * join anything. Callers order by this so precise relations are offered ahead
44
+ * of general ones.
45
+ */
46
+ export function relationshipSpecificity(definition) {
47
+ return (definition.sourceEnd.type ? 1 : 0) + (definition.targetEnd.type ? 1 : 0);
48
+ }
49
+ /**
50
+ * True for a relation that can join any two elements because it types neither
51
+ * end — MemoLink and anything else declared the same way. These are the
52
+ * fallback when no specific relation carries the meaning, so they are offered
53
+ * last rather than suppressed.
54
+ */
55
+ export function isUniversalRelationship(definition) {
56
+ return relationshipSpecificity(definition) === 0;
57
+ }
58
+ /** Order definitions most-specific first, then alphabetically for stability. */
59
+ function bySpecificity(a, b) {
60
+ return relationshipSpecificity(b) - relationshipSpecificity(a)
61
+ || a.label.localeCompare(b.label);
62
+ }
63
+ // ─── Registry lookup helpers ────────────────────────────────────────────────
64
+ /** Index kind definitions by name for O(1) supertype walks. */
65
+ export function indexKinds(kinds) {
66
+ return new Map(kinds.map(k => [k.name, k]));
67
+ }
68
+ /**
69
+ * Resolve a relationship definition by camelCase name or PascalCase sysmlName.
70
+ * Callers may hold either form (the model uses camelCase, SysML source uses
71
+ * PascalCase), so both resolve to the same definition.
72
+ */
73
+ export function findRelationshipDefinition(type, registries) {
74
+ if (!type)
75
+ return undefined;
76
+ return registries.relationships.find(r => r.name === type || r.sysmlName === type);
77
+ }
78
+ // ─── kindConformsTo ─────────────────────────────────────────────────────────
79
+ /**
80
+ * True when `actualKind` may occupy an end declared as `expectedKind`.
81
+ *
82
+ * Conformance is transitive over the kind specialization chain, so
83
+ * HardwareAssembly :> ArchitectureElement :> MemoPart conforms to all three.
84
+ * An undefined or empty expected type accepts any kind (untyped end).
85
+ */
86
+ export function kindConformsTo(actualKind, expectedKind, kindRegistry) {
87
+ if (!expectedKind)
88
+ return true; // untyped end accepts anything
89
+ if (!actualKind)
90
+ return false;
91
+ if (actualKind === expectedKind)
92
+ return true;
93
+ const index = Array.isArray(kindRegistry) ? indexKinds(kindRegistry) : kindRegistry;
94
+ let current = actualKind;
95
+ const seen = new Set();
96
+ while (current && !seen.has(current)) {
97
+ if (current === expectedKind)
98
+ return true;
99
+ seen.add(current);
100
+ current = index.get(current)?.superType;
101
+ }
102
+ return false;
103
+ }
104
+ // ─── legalRelationshipDirections ────────────────────────────────────────────
105
+ /**
106
+ * Which ends of `definition` the element may occupy.
107
+ *
108
+ * 'outgoing' means the element can sit on the source end (element → other);
109
+ * 'incoming' means it can sit on the target end (other → element). An element
110
+ * whose kind conforms to both ends legally supports both directions.
111
+ */
112
+ export function legalRelationshipDirections(selectedElement, definition, registries) {
113
+ if (definition.isAbstract)
114
+ return [];
115
+ const kinds = indexKinds(registries.kinds);
116
+ const directions = [];
117
+ if (kindConformsTo(selectedElement.kind, definition.sourceEnd.type, kinds))
118
+ directions.push('outgoing');
119
+ if (kindConformsTo(selectedElement.kind, definition.targetEnd.type, kinds))
120
+ directions.push('incoming');
121
+ return directions;
122
+ }
123
+ /**
124
+ * Every relationship type the element can participate in, with the directions
125
+ * it may take. This is what the Properties panel lists under Add Relationship
126
+ * before the user has picked an opposite endpoint.
127
+ */
128
+ export function legalRelationshipsForElement(selectedElement, registries) {
129
+ const result = [];
130
+ for (const definition of registries.relationships) {
131
+ const directions = legalRelationshipDirections(selectedElement, definition, registries);
132
+ if (directions.length > 0)
133
+ result.push({ definition, directions });
134
+ }
135
+ // Specific relations first; universal ones like MemoLink fall to the end.
136
+ return result.sort((a, b) => bySpecificity(a.definition, b.definition));
137
+ }
138
+ /**
139
+ * All relationship types legal between two specific elements, in both
140
+ * directions. Backs the target-first workflow: pick any element, then see only
141
+ * the links that could actually connect the two.
142
+ *
143
+ * `direction` is stated relative to `sourceElement`, so an 'incoming' option
144
+ * means sourceElement occupies the target end.
145
+ */
146
+ export function legalRelationshipTypes(sourceElement, targetElement, registries) {
147
+ const kinds = indexKinds(registries.kinds);
148
+ const options = [];
149
+ for (const definition of registries.relationships) {
150
+ if (definition.isAbstract)
151
+ continue;
152
+ // Forward: sourceElement on the source end, targetElement on the target end.
153
+ if (kindConformsTo(sourceElement.kind, definition.sourceEnd.type, kinds) &&
154
+ kindConformsTo(targetElement.kind, definition.targetEnd.type, kinds)) {
155
+ options.push({
156
+ definition,
157
+ direction: 'outgoing',
158
+ sourceId: sourceElement.id,
159
+ targetId: targetElement.id,
160
+ });
161
+ }
162
+ // Reverse: targetElement on the source end, sourceElement on the target end.
163
+ if (kindConformsTo(targetElement.kind, definition.sourceEnd.type, kinds) &&
164
+ kindConformsTo(sourceElement.kind, definition.targetEnd.type, kinds)) {
165
+ options.push({
166
+ definition,
167
+ direction: 'incoming',
168
+ sourceId: targetElement.id,
169
+ targetId: sourceElement.id,
170
+ });
171
+ }
172
+ }
173
+ // Specific relations first, so a universal fallback never displaces a
174
+ // relation that actually carries the meaning.
175
+ return options.sort((a, b) => bySpecificity(a.definition, b.definition));
176
+ }
177
+ /**
178
+ * Every element in the loaded model that may legally occupy the opposite end.
179
+ *
180
+ * Searches the complete model — not only what the active diagram shows — and
181
+ * excludes the selected element itself, since self-relationships are prohibited.
182
+ */
183
+ export function compatibleRelationshipTargets(selectedElement, definition, direction, model, registries, filter) {
184
+ const kinds = indexKinds(registries.kinds);
185
+ // Outgoing: the selected element holds the source end, so candidates must
186
+ // conform to the target end — and the reverse for incoming.
187
+ const oppositeEnd = direction === 'outgoing' ? definition.targetEnd : definition.sourceEnd;
188
+ // The selected element must legally hold its own end, or nothing is offered.
189
+ const ownEnd = direction === 'outgoing' ? definition.sourceEnd : definition.targetEnd;
190
+ if (!kindConformsTo(selectedElement.kind, ownEnd.type, kinds))
191
+ return [];
192
+ const query = filter?.query?.trim().toLowerCase();
193
+ const candidates = [];
194
+ for (const element of Object.values(model.elements)) {
195
+ if (element.id === selectedElement.id)
196
+ continue; // no self-links
197
+ if (!kindConformsTo(element.kind, oppositeEnd.type, kinds))
198
+ continue;
199
+ if (filter?.kind && element.kind !== filter.kind)
200
+ continue;
201
+ if (filter?.layer && element.layer !== filter.layer)
202
+ continue;
203
+ if (filter?.package && element.package !== filter.package)
204
+ continue;
205
+ if (query) {
206
+ const haystack = [element.name, element.id, element.shortId, element.kind]
207
+ .filter(Boolean).join(' ').toLowerCase();
208
+ if (!haystack.includes(query))
209
+ continue;
210
+ }
211
+ candidates.push({
212
+ element,
213
+ kind: element.kind,
214
+ layer: element.layer,
215
+ package: element.package,
216
+ });
217
+ }
218
+ candidates.sort((a, b) => a.layer.localeCompare(b.layer) ||
219
+ a.kind.localeCompare(b.kind) ||
220
+ a.element.name.localeCompare(b.element.name));
221
+ return candidates;
222
+ }
223
+ /** Group candidates by kind within layer, for the picker's section headers. */
224
+ export function groupTargetsByKindAndLayer(candidates) {
225
+ const groups = new Map();
226
+ for (const candidate of candidates) {
227
+ const key = `${candidate.layer}${candidate.kind}`;
228
+ let group = groups.get(key);
229
+ if (!group) {
230
+ group = { layer: candidate.layer, kind: candidate.kind, elements: [] };
231
+ groups.set(key, group);
232
+ }
233
+ group.elements.push(candidate.element);
234
+ }
235
+ return Array.from(groups.values());
236
+ }
237
+ /**
238
+ * The complete legality check for a create request. The client runs this to
239
+ * shape the UI; the server runs the identical function again before touching a
240
+ * file, so a stale or hand-crafted request cannot bypass the ontology.
241
+ */
242
+ export function validateRelationshipMutation(request, model, registries, context) {
243
+ const diagnostics = [];
244
+ const error = (code, message) => diagnostics.push({ code, message, severity: 'error' });
245
+ // REL-001 — the relationship type must exist in the ontology.
246
+ const definition = findRelationshipDefinition(request.type, registries);
247
+ if (!definition) {
248
+ error('REL-001', `Unknown relationship type "${request.type}"`);
249
+ return { valid: false, diagnostics };
250
+ }
251
+ if (definition.isAbstract) {
252
+ error('REL-001', `Relationship type "${definition.sysmlName}" is abstract and cannot be instantiated`);
253
+ return { valid: false, diagnostics, normalizedType: definition.name, definition };
254
+ }
255
+ const normalizedType = definition.name;
256
+ // REL-006 — both endpoints must exist in the loaded model.
257
+ const source = model.elements[request.sourceId];
258
+ const target = model.elements[request.targetId];
259
+ if (!source)
260
+ error('REL-006', `Source element "${request.sourceId}" does not exist in the model`);
261
+ if (!target)
262
+ error('REL-006', `Target element "${request.targetId}" does not exist in the model`);
263
+ if (!source || !target)
264
+ return { valid: false, diagnostics, normalizedType, definition };
265
+ // REL-007 — self-links, permitted only where the relation declares itself
266
+ // reflexive. This is the ontology's call, not a universal truth: some
267
+ // relations legitimately relate an element to itself.
268
+ if (request.sourceId === request.targetId && !allowsSelfLink(definition)) {
269
+ error('REL-007', `${source.name} cannot be related to itself by ${definition.label}`);
270
+ }
271
+ const kinds = indexKinds(registries.kinds);
272
+ // REL-002 — each endpoint's kind must conform to its declared end type.
273
+ if (!kindConformsTo(source.kind, definition.sourceEnd.type, kinds)) {
274
+ error('REL-002', `${source.kind} cannot occupy the ${definition.sourceEnd.name} end of ${definition.label}`);
275
+ }
276
+ if (!kindConformsTo(target.kind, definition.targetEnd.type, kinds)) {
277
+ error('REL-002', `${target.kind} cannot occupy the ${definition.targetEnd.name} end of ${definition.label}`);
278
+ }
279
+ // REL-008 — the stated direction must match the ends the elements occupy.
280
+ // The request is always normalized to source→target, so the element the
281
+ // user selected has to sit on the end its chosen direction implies.
282
+ if (request.selectedElementId) {
283
+ const expectedId = request.direction === 'outgoing' ? request.sourceId : request.targetId;
284
+ if (request.selectedElementId !== expectedId) {
285
+ error('REL-008', `Direction "${request.direction}" does not place ${model.elements[request.selectedElementId]?.name ?? request.selectedElementId} on the ${request.direction === 'outgoing' ? definition.sourceEnd.name : definition.targetEnd.name} end of ${definition.label}`);
286
+ }
287
+ }
288
+ // REL-003 — repeats of the same ordered pair, refused unless the relation
289
+ // declares itself non-unique.
290
+ if (!allowsDuplicates(definition)) {
291
+ const duplicate = model.relationships.find(rel => rel.type === normalizedType &&
292
+ rel.sourceId === request.sourceId &&
293
+ rel.targetId === request.targetId);
294
+ if (duplicate) {
295
+ error('REL-003', `This exact relationship already exists (${duplicate.id})`);
296
+ }
297
+ }
298
+ // REL-004 — declared end multiplicity caps how many links an endpoint takes.
299
+ const sourceViolation = multiplicityViolation(definition, 'source', request, model, normalizedType);
300
+ if (sourceViolation)
301
+ error('REL-004', sourceViolation);
302
+ const targetViolation = multiplicityViolation(definition, 'target', request, model, normalizedType);
303
+ if (targetViolation)
304
+ error('REL-004', targetViolation);
305
+ // REL-009 — the relationship has to be expressible from a writable package.
306
+ if (context?.hasWritableOwner === false) {
307
+ error('REL-009', 'No writable project package can own this relationship');
308
+ }
309
+ // REL-005 — a warning, not an error: the model accepts the relationship, the
310
+ // active view simply will not display it. Creation still proceeds.
311
+ if (context?.viewProfile) {
312
+ const permitted = context.viewProfile.permittedRelationshipTypes;
313
+ if (permitted && permitted.length > 0 && !permitted.includes(normalizedType)) {
314
+ diagnostics.push({
315
+ code: 'REL-005',
316
+ severity: 'warning',
317
+ message: `The relationship is valid in the model but is not permitted in the active ${context.viewProfile.diagramId} view`,
318
+ });
319
+ }
320
+ }
321
+ return {
322
+ valid: !diagnostics.some(d => d.severity === 'error'),
323
+ diagnostics,
324
+ normalizedType,
325
+ definition,
326
+ };
327
+ }
328
+ /**
329
+ * Check one end's declared upper bound against links already in the model.
330
+ * Returns a message when adding this relationship would exceed it.
331
+ */
332
+ function multiplicityViolation(definition, end, request, model, normalizedType) {
333
+ const endDef = end === 'source' ? definition.sourceEnd : definition.targetEnd;
334
+ const upper = endDef.multiplicity?.upper;
335
+ if (upper === undefined || upper === null)
336
+ return undefined; // undeclared or unbounded
337
+ // A bound on the source end limits how many source elements one target may
338
+ // have, and vice versa — it constrains the opposite endpoint's link count.
339
+ const anchorId = end === 'source' ? request.targetId : request.sourceId;
340
+ const existing = model.relationships.filter(rel => rel.type === normalizedType &&
341
+ (end === 'source' ? rel.targetId === anchorId : rel.sourceId === anchorId));
342
+ if (existing.length >= upper) {
343
+ const anchor = model.elements[anchorId];
344
+ return `${anchor?.kind ?? 'Element'} ${anchor?.name ?? anchorId} already has the maximum permitted ${endDef.name} (${upper}) for ${definition.label}`;
345
+ }
346
+ return undefined;
347
+ }
348
+ /** Validate a delete request against the loaded model. */
349
+ export function validateRelationshipDeletion(request, model) {
350
+ const relationship = model.relationships.find(rel => rel.id === request.relationshipId);
351
+ if (!relationship) {
352
+ return {
353
+ valid: false,
354
+ diagnostics: [{
355
+ code: 'REL-010',
356
+ severity: 'error',
357
+ message: `Relationship "${request.relationshipId}" was not found in the model`,
358
+ }],
359
+ };
360
+ }
361
+ return { valid: true, diagnostics: [], relationship };
362
+ }
363
+ // ─── Presentation helpers ───────────────────────────────────────────────────
364
+ /**
365
+ * Plain-language preview of a pending relationship, e.g.
366
+ * "Infusion Controller — satisfies → Software Requirement SR-104".
367
+ * Shared so the Properties panel and the diagram picker word it identically.
368
+ */
369
+ export function describeRelationship(definition, source, target, options) {
370
+ const sourceLabel = source?.name ?? options?.sourceId ?? 'Unknown';
371
+ const targetLabel = target ? `${target.kind} ${target.name}` : (options?.targetId ?? 'Unknown');
372
+ return `${sourceLabel} — ${definition.label.toLowerCase()} → ${targetLabel}`;
373
+ }
374
+ /**
375
+ * Whether an existing relationship should be drawn in a given view: both
376
+ * endpoints selected by the view, and the type permitted by its profile.
377
+ */
378
+ export function isRelationshipVisibleInView(relationship, profile) {
379
+ const permitted = profile.permittedRelationshipTypes;
380
+ if (permitted && permitted.length > 0 && !permitted.includes(relationship.type))
381
+ return false;
382
+ const elementIds = profile.elementIds;
383
+ if (elementIds && elementIds.length > 0) {
384
+ if (!elementIds.includes(relationship.sourceId))
385
+ return false;
386
+ if (!elementIds.includes(relationship.targetId))
387
+ return false;
388
+ }
389
+ return true;
390
+ }
391
+ //# sourceMappingURL=relationship-legality.js.map