@memoarchitect/tools 0.7.2 → 0.8.8

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 (167) hide show
  1. package/README.md +1 -1
  2. package/corpus/README.md +63 -0
  3. package/corpus/sysml-v2-release/bnf/SysML-graphical-bnf.kgbnf +2264 -0
  4. package/package.json +4 -3
  5. package/packages/tools/lib/analysis/dsm.d.ts +1 -1
  6. package/packages/tools/lib/analysis/dsm.d.ts.map +1 -1
  7. package/packages/tools/lib/analysis/dsm.js +1 -1
  8. package/packages/tools/lib/analysis/dsm.js.map +1 -1
  9. package/packages/tools/lib/bin/memo.js +25 -0
  10. package/packages/tools/lib/bin/memo.js.map +1 -1
  11. package/packages/tools/lib/browser/index.d.ts +2 -0
  12. package/packages/tools/lib/browser/index.d.ts.map +1 -1
  13. package/packages/tools/lib/browser/index.js +1 -0
  14. package/packages/tools/lib/browser/index.js.map +1 -1
  15. package/packages/tools/lib/commands/dev.d.ts.map +1 -1
  16. package/packages/tools/lib/commands/dev.js +37 -15
  17. package/packages/tools/lib/commands/dev.js.map +1 -1
  18. package/packages/tools/lib/commands/dhf-lint.d.ts +10 -0
  19. package/packages/tools/lib/commands/dhf-lint.d.ts.map +1 -0
  20. package/packages/tools/lib/commands/dhf-lint.js +72 -0
  21. package/packages/tools/lib/commands/dhf-lint.js.map +1 -0
  22. package/packages/tools/lib/commands/dhf.d.ts.map +1 -1
  23. package/packages/tools/lib/commands/dhf.js +7 -1
  24. package/packages/tools/lib/commands/dhf.js.map +1 -1
  25. package/packages/tools/lib/commands/standards.d.ts +28 -0
  26. package/packages/tools/lib/commands/standards.d.ts.map +1 -0
  27. package/packages/tools/lib/commands/standards.js +203 -0
  28. package/packages/tools/lib/commands/standards.js.map +1 -0
  29. package/packages/tools/lib/commands/validate.d.ts.map +1 -1
  30. package/packages/tools/lib/commands/validate.js +13 -2
  31. package/packages/tools/lib/commands/validate.js.map +1 -1
  32. package/packages/tools/lib/dhf/document-compiler.d.ts.map +1 -1
  33. package/packages/tools/lib/dhf/document-compiler.js +13 -8
  34. package/packages/tools/lib/dhf/document-compiler.js.map +1 -1
  35. package/packages/tools/lib/dhf/document-registry.d.ts +16 -2
  36. package/packages/tools/lib/dhf/document-registry.d.ts.map +1 -1
  37. package/packages/tools/lib/dhf/document-registry.js +74 -23
  38. package/packages/tools/lib/dhf/document-registry.js.map +1 -1
  39. package/packages/tools/lib/dhf/index.d.ts +3 -0
  40. package/packages/tools/lib/dhf/index.d.ts.map +1 -1
  41. package/packages/tools/lib/dhf/index.js +4 -0
  42. package/packages/tools/lib/dhf/index.js.map +1 -1
  43. package/packages/tools/lib/dhf/query-engine.d.ts +12 -1
  44. package/packages/tools/lib/dhf/query-engine.d.ts.map +1 -1
  45. package/packages/tools/lib/dhf/query-engine.js +2 -1
  46. package/packages/tools/lib/dhf/query-engine.js.map +1 -1
  47. package/packages/tools/lib/dhf/query-executor.d.ts +72 -5
  48. package/packages/tools/lib/dhf/query-executor.d.ts.map +1 -1
  49. package/packages/tools/lib/dhf/query-executor.js +471 -81
  50. package/packages/tools/lib/dhf/query-executor.js.map +1 -1
  51. package/packages/tools/lib/dhf/script-runner.js +6 -6
  52. package/packages/tools/lib/dhf/script-runner.js.map +1 -1
  53. package/packages/tools/lib/dhf/standards-block.d.ts +40 -0
  54. package/packages/tools/lib/dhf/standards-block.d.ts.map +1 -0
  55. package/packages/tools/lib/dhf/standards-block.js +223 -0
  56. package/packages/tools/lib/dhf/standards-block.js.map +1 -0
  57. package/packages/tools/lib/dhf/standards-library.d.ts +123 -0
  58. package/packages/tools/lib/dhf/standards-library.d.ts.map +1 -0
  59. package/packages/tools/lib/dhf/standards-library.js +295 -0
  60. package/packages/tools/lib/dhf/standards-library.js.map +1 -0
  61. package/packages/tools/lib/dhf/standards-report.d.ts +189 -0
  62. package/packages/tools/lib/dhf/standards-report.d.ts.map +1 -0
  63. package/packages/tools/lib/dhf/standards-report.js +384 -0
  64. package/packages/tools/lib/dhf/standards-report.js.map +1 -0
  65. package/packages/tools/lib/dhf/template-lint.d.ts +42 -0
  66. package/packages/tools/lib/dhf/template-lint.d.ts.map +1 -0
  67. package/packages/tools/lib/dhf/template-lint.js +421 -0
  68. package/packages/tools/lib/dhf/template-lint.js.map +1 -0
  69. package/packages/tools/lib/dhf/template-resolver.d.ts.map +1 -1
  70. package/packages/tools/lib/dhf/template-resolver.js +17 -5
  71. package/packages/tools/lib/dhf/template-resolver.js.map +1 -1
  72. package/packages/tools/lib/importer/cameo-importer.js +2 -2
  73. package/packages/tools/lib/importer/cameo-importer.js.map +1 -1
  74. package/packages/tools/lib/importer/ea-importer.d.ts.map +1 -1
  75. package/packages/tools/lib/importer/ea-importer.js +4 -3
  76. package/packages/tools/lib/importer/ea-importer.js.map +1 -1
  77. package/packages/tools/lib/importer/owl-importer.d.ts.map +1 -1
  78. package/packages/tools/lib/importer/owl-importer.js +2 -3
  79. package/packages/tools/lib/importer/owl-importer.js.map +1 -1
  80. package/packages/tools/lib/language/generated/ast.d.ts +1172 -54
  81. package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
  82. package/packages/tools/lib/language/generated/ast.js +967 -39
  83. package/packages/tools/lib/language/generated/ast.js.map +1 -1
  84. package/packages/tools/lib/language/generated/grammar.d.ts.map +1 -1
  85. package/packages/tools/lib/language/generated/grammar.js +3694 -425
  86. package/packages/tools/lib/language/generated/grammar.js.map +1 -1
  87. package/packages/tools/lib/model/builder.d.ts.map +1 -1
  88. package/packages/tools/lib/model/builder.js +730 -48
  89. package/packages/tools/lib/model/builder.js.map +1 -1
  90. package/packages/tools/lib/model/kind-registry.d.ts +20 -0
  91. package/packages/tools/lib/model/kind-registry.d.ts.map +1 -1
  92. package/packages/tools/lib/model/kind-registry.js +38 -0
  93. package/packages/tools/lib/model/kind-registry.js.map +1 -1
  94. package/packages/tools/lib/model/manifest.d.ts +8 -0
  95. package/packages/tools/lib/model/manifest.d.ts.map +1 -1
  96. package/packages/tools/lib/model/manifest.js +3 -0
  97. package/packages/tools/lib/model/manifest.js.map +1 -1
  98. package/packages/tools/lib/model/naming.d.ts +57 -0
  99. package/packages/tools/lib/model/naming.d.ts.map +1 -0
  100. package/packages/tools/lib/model/naming.js +69 -0
  101. package/packages/tools/lib/model/naming.js.map +1 -0
  102. package/packages/tools/lib/model/native-project.d.ts +10 -7
  103. package/packages/tools/lib/model/native-project.d.ts.map +1 -1
  104. package/packages/tools/lib/model/native-project.js +76 -21
  105. package/packages/tools/lib/model/native-project.js.map +1 -1
  106. package/packages/tools/lib/model/ontology-loader.d.ts.map +1 -1
  107. package/packages/tools/lib/model/ontology-loader.js +3 -0
  108. package/packages/tools/lib/model/ontology-loader.js.map +1 -1
  109. package/packages/tools/lib/model/package-manifest.d.ts +4 -0
  110. package/packages/tools/lib/model/package-manifest.d.ts.map +1 -1
  111. package/packages/tools/lib/model/package-manifest.js +2 -0
  112. package/packages/tools/lib/model/package-manifest.js.map +1 -1
  113. package/packages/tools/lib/model/relationship-legality.d.ts +17 -0
  114. package/packages/tools/lib/model/relationship-legality.d.ts.map +1 -1
  115. package/packages/tools/lib/model/relationship-legality.js.map +1 -1
  116. package/packages/tools/lib/model/relationship-registry.d.ts +77 -0
  117. package/packages/tools/lib/model/relationship-registry.d.ts.map +1 -1
  118. package/packages/tools/lib/model/relationship-registry.js +217 -2
  119. package/packages/tools/lib/model/relationship-registry.js.map +1 -1
  120. package/packages/tools/lib/model/semantic.d.ts +48 -0
  121. package/packages/tools/lib/model/semantic.d.ts.map +1 -1
  122. package/packages/tools/lib/model/semantic.js +3 -0
  123. package/packages/tools/lib/model/semantic.js.map +1 -1
  124. package/packages/tools/lib/model/settings-boundary.js +1 -1
  125. package/packages/tools/lib/model/settings-boundary.js.map +1 -1
  126. package/packages/tools/lib/model/short-id.d.ts.map +1 -1
  127. package/packages/tools/lib/model/short-id.js +1 -3
  128. package/packages/tools/lib/model/short-id.js.map +1 -1
  129. package/packages/tools/lib/model/view-deriver.d.ts +10 -1
  130. package/packages/tools/lib/model/view-deriver.d.ts.map +1 -1
  131. package/packages/tools/lib/model/view-deriver.js +104 -5
  132. package/packages/tools/lib/model/view-deriver.js.map +1 -1
  133. package/packages/tools/lib/protocol/messages.d.ts +85 -3
  134. package/packages/tools/lib/protocol/messages.d.ts.map +1 -1
  135. package/packages/tools/lib/server/dev-server.d.ts.map +1 -1
  136. package/packages/tools/lib/server/dev-server.js +161 -5
  137. package/packages/tools/lib/server/dev-server.js.map +1 -1
  138. package/packages/tools/lib/server/package-source.d.ts +26 -0
  139. package/packages/tools/lib/server/package-source.d.ts.map +1 -0
  140. package/packages/tools/lib/server/package-source.js +81 -0
  141. package/packages/tools/lib/server/package-source.js.map +1 -0
  142. package/packages/tools/lib/server/package-writer.d.ts +94 -0
  143. package/packages/tools/lib/server/package-writer.d.ts.map +1 -0
  144. package/packages/tools/lib/server/package-writer.js +317 -0
  145. package/packages/tools/lib/server/package-writer.js.map +1 -0
  146. package/packages/tools/lib/server/persistor.d.ts +20 -0
  147. package/packages/tools/lib/server/persistor.d.ts.map +1 -1
  148. package/packages/tools/lib/server/persistor.js +29 -8
  149. package/packages/tools/lib/server/persistor.js.map +1 -1
  150. package/packages/tools/lib/server/relationship-writer.d.ts +13 -1
  151. package/packages/tools/lib/server/relationship-writer.d.ts.map +1 -1
  152. package/packages/tools/lib/server/relationship-writer.js +59 -0
  153. package/packages/tools/lib/server/relationship-writer.js.map +1 -1
  154. package/packages/tools/lib/server/sysml-notation.d.ts +1 -1
  155. package/packages/tools/lib/server/sysml-notation.d.ts.map +1 -1
  156. package/packages/tools/lib/server/sysml-notation.js +17 -0
  157. package/packages/tools/lib/server/sysml-notation.js.map +1 -1
  158. package/packages/tools/lib/server/view-layout-store.d.ts.map +1 -1
  159. package/packages/tools/lib/server/view-layout-store.js +1 -0
  160. package/packages/tools/lib/server/view-layout-store.js.map +1 -1
  161. package/packages/tools/lib/validator/behavior-validator.d.ts +1 -1
  162. package/packages/tools/lib/validator/behavior-validator.js +1 -1
  163. package/packages/tools/lib/validator/constraint-eval.d.ts +16 -0
  164. package/packages/tools/lib/validator/constraint-eval.d.ts.map +1 -1
  165. package/packages/tools/lib/validator/constraint-eval.js +102 -0
  166. package/packages/tools/lib/validator/constraint-eval.js.map +1 -1
  167. package/packages/tools/src/grammar/memo-sysml.langium +425 -26
@@ -21,14 +21,26 @@
21
21
  // - ActionUsage supports composite actions with nested actions
22
22
  // - FlowConnectionUsage → MemoRelationship of type "flow" with flowItem
23
23
  // - SuccessionUsage → MemoRelationship pairs of type "succession"
24
- // - AllocateUsage → MemoRelationship of type "allocateTo"
25
24
  // - ItemDefinition → MemoElement with construct "item"
25
+ //
26
+ // Language-native trace relations:
27
+ // - AllocateUsage → MemoRelationship "allocatedTo"
28
+ // - SatisfyRequirementUsage → MemoRelationship "satisfiedBy"
29
+ // - RequirementVerificationMember → MemoRelationship "verifiedBy"
30
+ // Each is the SAME edge the corresponding MEMO `connection def` produces —
31
+ // see LANGUAGE_NATIVE_RELATIONS. A model may use either spelling and the
32
+ // graph is identical, which is what makes the migration off the private
33
+ // connection defs reversible one file at a time.
26
34
  // ─────────────────────────────────────────────────────────────────────────────
27
35
  import { createHash } from 'node:crypto';
36
+ import { AstUtils } from 'langium';
37
+ import { isRequirementVerificationMember, isSatisfyRequirementUsage, isPerformActionUsage, isIncludeUseCaseUsage, isExhibitStateUsage, isFramedConcernMember, isSubjectMember, isDependency, isVariantMember, isActorMember, } from '../language/generated/ast.js';
28
38
  import { PackageRegistry } from './package-registry.js';
29
39
  import { assignSequentialShortIds, kindToPrefix } from './short-id.js';
40
+ import { LANGUAGE_NATIVE_RELATIONS, REFINEMENT_METADATA, pascalToCamelCase } from './relationship-registry.js';
30
41
  import { indexKinds, kindConformsTo } from './relationship-legality.js';
31
42
  import { CONTROL_KIND_METACLASS } from './activity-notation.js';
43
+ import { toModelType } from './naming.js';
32
44
  let relationshipCounter = 0;
33
45
  /**
34
46
  * Resolve a kind definition using registry-first, config-fallback strategy.
@@ -86,13 +98,14 @@ export function buildMemoModel(documents, config, parseErrors = [], registries)
86
98
  const deferredFlows = [];
87
99
  const deferredSuccessions = [];
88
100
  const deferredAllocates = [];
101
+ const deferredBindings = [];
89
102
  // Phase 1: Build package registry from all documents
90
103
  const registry = new PackageRegistry();
91
104
  registry.buildFromDocuments(documents);
92
105
  // Phase 2: Extract elements from all documents (populates registry)
93
106
  for (const { document, filePath } of documents) {
94
107
  const model = document.parseResult.value;
95
- extractFromModel(model, filePath, config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, errors, registry, registries);
108
+ extractFromModel(model, filePath, config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, deferredBindings, errors, registry, registries);
96
109
  }
97
110
  // Phase 3: Resolve connections using the registry (all elements now known)
98
111
  const allElementIds = new Set(elements.keys());
@@ -101,7 +114,11 @@ export function buildMemoModel(documents, config, parseErrors = [], registries)
101
114
  }
102
115
  // Phase 3b: Resolve flow connections
103
116
  for (const { flow, filePath, packageName, parentActionId } of deferredFlows) {
104
- resolveFlowConnection(flow, filePath, packageName, parentActionId, relationships, allElementIds);
117
+ resolveFlowConnection(flow, filePath, packageName, parentActionId, relationships, allElementIds, elements);
118
+ }
119
+ // Phase 3b2: Resolve port delegations
120
+ for (const { binding, filePath, packageName } of deferredBindings) {
121
+ resolveBinding(binding, filePath, packageName, relationships, allElementIds, elements);
105
122
  }
106
123
  // Phase 3c: Resolve successions
107
124
  for (const { succession, filePath, packageName, parentActionId } of deferredSuccessions) {
@@ -109,7 +126,70 @@ export function buildMemoModel(documents, config, parseErrors = [], registries)
109
126
  }
110
127
  // Phase 3d: Resolve allocations
111
128
  for (const { allocate, filePath, packageName } of deferredAllocates) {
112
- resolveAllocate(allocate, filePath, packageName, elements, relationships, registry, allElementIds);
129
+ resolveAllocate(allocate, filePath, packageName, elements, relationships, registry, allElementIds, registries?.relationshipRegistry);
130
+ }
131
+ // Phase 3e: Resolve the native requirement relations. Unlike connections
132
+ // these are not package members — a `satisfy` sits in the body of the part
133
+ // that is the design context, and a `verify` in the body of the case that
134
+ // does the verifying — so they are found by walking containment rather than
135
+ // by the package-member switch, and their context comes from their owner.
136
+ for (const { document, filePath } of documents) {
137
+ resolveNativeTraceUsages(document.parseResult.value, filePath, relationships, registry, allElementIds);
138
+ }
139
+ // Phase 3f: Synthesize containment edges from native nesting. A natively
140
+ // nested part carries its parent in `owner` (set during extraction); MEMO's
141
+ // consumers read containment as a `composes` relationship (parent → child),
142
+ // so emit one per nesting. A model that nests natively then looks identical
143
+ // to one that wrote `Composes`, which is what lets R10-S7 delete the
144
+ // relationship without the containment vanishing. Ports carry `owner` too,
145
+ // so this is confined to part-to-part nesting.
146
+ for (const el of elements.values()) {
147
+ if (el.construct !== 'part' || !el.owner)
148
+ continue;
149
+ const parent = elements.get(el.owner);
150
+ if (!parent || parent.construct !== 'part')
151
+ continue;
152
+ relationships.push({
153
+ id: `rel-${++relationshipCounter}`,
154
+ type: 'composes',
155
+ sourceId: el.owner,
156
+ sourceEnd: 'parent',
157
+ targetId: el.id,
158
+ targetEnd: 'child',
159
+ file: el.file,
160
+ named: false,
161
+ });
162
+ }
163
+ // A nested action carries its parent in `parentAction` rather than
164
+ // `owner` (the two are set by separate extraction paths), and
165
+ // CR-ONT-065/066 already treat an action-typed end as a legitimate
166
+ // composes participant alongside parts — a functional decomposition
167
+ // (`action fnParent { action fnChild : SystemFunction { ... } }`) needs
168
+ // the same synthesis or a natively nested function tree loses the
169
+ // `composes` edges DSM and traceability consumers read, the exact
170
+ // silent-drop shape R10-S7 exists to close, not open. Every action
171
+ // usage nests some way — a flow step under its flow, a control node
172
+ // under its action — so this is not confined to decomposition the way
173
+ // the part path is; a nested action's owner IS its containing action
174
+ // for every shape the grammar produces, so there is no narrower
175
+ // predicate to gate this on without re-deriving one from AST shape
176
+ // that `parentAction` already erased.
177
+ for (const el of elements.values()) {
178
+ if (el.construct !== 'action' || !el.parentAction)
179
+ continue;
180
+ const parent = elements.get(el.parentAction);
181
+ if (!parent || parent.construct !== 'action')
182
+ continue;
183
+ relationships.push({
184
+ id: `rel-${++relationshipCounter}`,
185
+ type: 'composes',
186
+ sourceId: el.parentAction,
187
+ sourceEnd: 'parent',
188
+ targetId: el.id,
189
+ targetEnd: 'child',
190
+ file: el.file,
191
+ named: false,
192
+ });
113
193
  }
114
194
  // Keep ownership of a declaration separate from ownership of the type that
115
195
  // classifies it. A project usage of `Hazard` is editable project content;
@@ -139,12 +219,16 @@ export function buildMemoModel(documents, config, parseErrors = [], registries)
139
219
  const idToShortId = assignSequentialShortIds(prefix, prefixElements.map(e => e.id));
140
220
  for (const el of prefixElements) {
141
221
  el.shortId = idToShortId.get(el.id);
142
- el.uuid = stableElementUuid(el);
222
+ // `uuid` is part of the ontology's identification core, so a model
223
+ // may author it. An authored value wins: it is the whole point of
224
+ // writing one — the element keeps its identity across a file move
225
+ // or an id change, which the derived hash cannot survive.
226
+ el.uuid = el.attributes.uuid?.trim() || stableElementUuid(el);
143
227
  }
144
228
  }
145
229
  // Phase 4: Validate relationship end types (warnings only).
146
230
  // A kind conforms to an end type if it equals it or specializes it
147
- // (transitively) — e.g. HardwareAssembly conforms to ArchitectureElement.
231
+ // (transitively) — e.g. HardwareAssembly conforms to MemoPart.
148
232
  if (registries?.relationshipRegistry) {
149
233
  // Same resolver the authoring UI and the server persistence path use,
150
234
  // so a link the panel offers is a link the builder accepts.
@@ -197,6 +281,7 @@ export function buildMemoModel(documents, config, parseErrors = [], registries)
197
281
  elements,
198
282
  relationships,
199
283
  errors,
284
+ packages: collectPackages(registry),
200
285
  elementsByKind,
201
286
  elementsByLayer,
202
287
  relationshipsByType,
@@ -204,6 +289,26 @@ export function buildMemoModel(documents, config, parseErrors = [], registries)
204
289
  incoming,
205
290
  };
206
291
  }
292
+ /**
293
+ * The declared packages, in the shape the model carries them.
294
+ *
295
+ * Read from the registry rather than from element membership so a package that
296
+ * declares nothing is still reported: an explorer builds its containment tree
297
+ * from this list, and a newly created package has no members yet by definition.
298
+ */
299
+ function collectPackages(registry) {
300
+ const packages = [];
301
+ for (const entry of registry.getPackages().values()) {
302
+ const separator = entry.qualifiedName.lastIndexOf('::');
303
+ packages.push({
304
+ qualifiedName: entry.qualifiedName,
305
+ name: separator === -1 ? entry.qualifiedName : entry.qualifiedName.slice(separator + 2),
306
+ ...(separator === -1 ? {} : { parent: entry.qualifiedName.slice(0, separator) }),
307
+ file: entry.file,
308
+ });
309
+ }
310
+ return packages.sort((a, b) => a.qualifiedName.localeCompare(b.qualifiedName));
311
+ }
207
312
  function applySemanticProvenance(elements, relationships, registries) {
208
313
  const table = registries?.provenance;
209
314
  if (!table)
@@ -263,19 +368,19 @@ function stableElementUuid(element) {
263
368
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
264
369
  }
265
370
  // ─── AST Walking ────────────────────────────────────────────────────────────
266
- function extractFromModel(model, filePath, config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, errors, registry, registries) {
371
+ function extractFromModel(model, filePath, config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, deferredBindings, errors, registry, registries) {
267
372
  for (const member of model.members) {
268
373
  if (member.$type === 'PackageDeclaration') {
269
- extractFromPackage(member, filePath, '', config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, errors, registry, registries);
374
+ extractFromPackage(member, filePath, '', config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, deferredBindings, errors, registry, registries);
270
375
  }
271
376
  }
272
377
  }
273
- function extractFromPackage(pkg, filePath, parentPackage, config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, errors, registry, registries) {
378
+ function extractFromPackage(pkg, filePath, parentPackage, config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, deferredBindings, errors, registry, registries) {
274
379
  const packageName = parentPackage ? `${parentPackage}::${pkg.name}` : pkg.name;
275
380
  for (const member of pkg.members) {
276
381
  switch (member.$type) {
277
382
  case 'PackageDeclaration':
278
- extractFromPackage(member, filePath, packageName, config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, errors, registry, registries);
383
+ extractFromPackage(member, filePath, packageName, config, elements, deferredConnections, deferredFlows, deferredSuccessions, deferredAllocates, deferredBindings, errors, registry, registries);
279
384
  break;
280
385
  case 'PartUsage':
281
386
  extractUsage(member, 'part', filePath, packageName, config, elements, registry, registries);
@@ -319,6 +424,16 @@ function extractFromPackage(pkg, filePath, parentPackage, config, elements, defe
319
424
  packageName,
320
425
  });
321
426
  break;
427
+ case 'BindingUsage':
428
+ // Port delegation. Deferred for the same reason as flows: both
429
+ // ends are dotted paths that only resolve once every nested
430
+ // port is an element.
431
+ deferredBindings.push({
432
+ binding: member,
433
+ filePath,
434
+ packageName,
435
+ });
436
+ break;
322
437
  case 'FlowConnectionUsage':
323
438
  // A flow between two action usages declared side by side in a
324
439
  // package has no enclosing action, so parentActionId is unset
@@ -329,6 +444,16 @@ function extractFromPackage(pkg, filePath, parentPackage, config, elements, defe
329
444
  packageName,
330
445
  });
331
446
  break;
447
+ case 'MessageUsage':
448
+ // `Message : FlowUsage`: native messages deliberately lower
449
+ // through the same path as MEMO's flow-shaped compatibility
450
+ // vocabulary, so consumers retain one trace-edge spelling.
451
+ deferredFlows.push({
452
+ flow: member,
453
+ filePath,
454
+ packageName,
455
+ });
456
+ break;
332
457
  case 'SuccessionUsage':
333
458
  deferredSuccessions.push({
334
459
  succession: member,
@@ -343,6 +468,27 @@ function extractFromPackage(pkg, filePath, parentPackage, config, elements, defe
343
468
  packageName,
344
469
  });
345
470
  break;
471
+ // ─── Native declarations (plan §8) ──────────────────────────
472
+ //
473
+ // `actor u : User;`, `stakeholder s : Stakeholder;` and
474
+ // `concern c : Concern;` declare the same elements `part u : User;`
475
+ // declares — same id, same kind, same attributes, same doc. Only
476
+ // the metaclass differs, and the metaclass is what makes them
477
+ // legible to a conforming tool. Routing them through extractUsage
478
+ // is what keeps the migration in step 3 a spelling change rather
479
+ // than a content change.
480
+ case 'ActorMember':
481
+ extractUsage(member, 'actor', filePath, packageName, config, elements, registry, registries);
482
+ break;
483
+ case 'StakeholderMember':
484
+ extractUsage(member, 'stakeholder', filePath, packageName, config, elements, registry, registries);
485
+ break;
486
+ case 'ConcernUsage':
487
+ extractUsage(member, 'concern', filePath, packageName, config, elements, registry, registries);
488
+ break;
489
+ case 'TransitionUsage':
490
+ extractTransition(member, filePath, packageName, config, elements, registry, registries);
491
+ break;
346
492
  // ─── Definition members ─────────────────────────────────────
347
493
  case 'ActionDefinition':
348
494
  extractActionDefinition(member, filePath, packageName, config, elements, deferredFlows, deferredSuccessions, registry, registries);
@@ -370,19 +516,35 @@ function extractUsage(usage, construct, filePath, packageName, config, elements,
370
516
  : { kindDef: undefined, resolvedKind: 'Unknown' };
371
517
  const attributes = extractAttributes(usage.body);
372
518
  const doc = extractDocComment(usage.body);
373
- // Nested part members: capture reference bindings (`part viewpoint :> vp;`)
374
- // and one level of nested part bodies (`part selectionQuery { ... }`) as
375
- // plain / prefixed attributes, so SysML-modelled views expose their
376
- // viewpoint binding and selection-query metadata to consumers.
519
+ // A view's `expose <path>;` members declare its element membership
520
+ // natively — SysML v2's own visibility mechanism, in place of the MEMO
521
+ // `IncludedIn` relationship. Collected as a comma-separated attribute so
522
+ // `resolveViewElementIds` in view-deriver.ts can resolve each path
523
+ // against the model the same way it resolves `selectionQuery.*`.
524
+ if (construct === 'view') {
525
+ const exposePaths = (usage.body || [])
526
+ .filter(member => member.$type === 'ExposeMember')
527
+ .map(member => member.path);
528
+ if (exposePaths.length > 0) {
529
+ attributes['expose'] = exposePaths.join(',');
530
+ }
531
+ }
532
+ // Nested part/reference members that are view METADATA, not containment:
533
+ // bindings (`part/ref viewpoint :> vp;`) and the `:>>` redefinition of an
534
+ // inherited feature whose body is attributes (`part :>> selectionQuery
535
+ // { ... }`). These are flattened onto the owner as plain / prefixed
536
+ // attributes so SysML-modelled views expose their viewpoint binding and
537
+ // selection-query metadata to consumers. A typed nested part is NOT metadata
538
+ // — it is real containment and is extracted as its own element below.
377
539
  for (const member of usage.body || []) {
378
- if (member.$type !== 'PartMember')
540
+ if (member.$type !== 'PartMember' && member.$type !== 'RefMember')
379
541
  continue;
380
542
  const pm = member;
381
543
  if (pm.boundRef && pm.name) {
382
- // Two shapes land here. `part viewpointDefinition :> vp;` names the
383
- // feature in `name` and the value in `boundRef`. The subsetting
544
+ // Two shapes land here. `part/ref viewpointDefinition :> vp;` names
545
+ // the feature in `name` and the value in `boundRef`. The subsetting
384
546
  // form for a multi-valued feature —
385
- // `part vpLogical :> viewpointDefinition = someViewpoint;` — puts
547
+ // `part/ref vpLogical :> viewpointDefinition = someViewpoint;` — puts
386
548
  // the FEATURE in `boundRef` and the value in `subsetValue`, and its
387
549
  // member name is arbitrary. Attribute it to the feature it subsets,
388
550
  // or a view governed by three viewpoints would surface as three
@@ -395,7 +557,13 @@ function extractUsage(usage, construct, filePath, packageName, config, elements,
395
557
  const previous = attributes[key];
396
558
  attributes[key] = previous ? `${previous},${value}` : value;
397
559
  }
398
- else if (pm.body && pm.name) {
560
+ else if (!pm.type && pm.body && pm.name) {
561
+ // R10-S3 guard: an UNTYPED nested part with an attribute body is
562
+ // view metadata — the `part :>> selectionQuery { ... }` (or the
563
+ // un-redefined `part selectionQuery { ... }`) that view derivation
564
+ // reads as `selectionQuery.*` attributes. It carries no kind, so it
565
+ // is not a contained element. Flatten it; a TYPED nested part
566
+ // (`part valve : Valve`) is real containment and is extracted below.
399
567
  const nested = extractAttributes(pm.body);
400
568
  for (const [k, v] of Object.entries(nested)) {
401
569
  attributes[`${pm.name}.${k}`] = v;
@@ -438,6 +606,88 @@ function extractUsage(usage, construct, filePath, packageName, config, elements,
438
606
  }
439
607
  elements.set(id, element);
440
608
  registry.registerElement(id, packageName);
609
+ // A port's body may declare further ports. Recurse only after the parent is
610
+ // in the map, so the child's `owner` points at an element that exists.
611
+ if (construct === 'port') {
612
+ extractDefinitionPorts(usage, filePath, packageName, config, elements, registry, registries);
613
+ }
614
+ // A part's body may nest further parts. SysML v2 expresses containment by
615
+ // nesting; MEMO historically expressed it with a `Composes` connection, and
616
+ // the two must yield the same model. Extract each nested part as its own
617
+ // element with `owner` set — exactly as a nested port is — only after the
618
+ // parent is in the map. buildMemoModel synthesizes the `composes` edge from
619
+ // the resulting `owner` chain.
620
+ //
621
+ // A part usage may also declare its OWN boundary ports directly in its
622
+ // body (`part x : T { in port p : DataPort; }`), distinct from a nested
623
+ // part's ports and from any ports the type `T` declares at the def level
624
+ // (that path is `extractDefinitionPorts` at package scope, line ~668).
625
+ // Without this, an inline-authored port silently has no element: it
626
+ // parses, every relationship endpoint referencing it resolves to a
627
+ // dangling id, and nothing downstream reports why.
628
+ if (construct === 'part') {
629
+ extractNestedParts(usage, filePath, packageName, config, elements, registry, registries);
630
+ extractDefinitionPorts(usage, filePath, packageName, config, elements, registry, registries);
631
+ }
632
+ // A native `transition` lives inside the state machine that owns it, which
633
+ // is where the language puts it and where MEMO's `part def Transition`
634
+ // never could. Extract it from the state usage's body so the two spellings
635
+ // yield the same element set.
636
+ for (const member of usage.body || []) {
637
+ if (member.$type === 'TransitionUsage') {
638
+ extractTransition(member, filePath, packageName, config, elements, registry, registries);
639
+ }
640
+ }
641
+ }
642
+ /**
643
+ * Extract a native `transition` as a MemoElement.
644
+ *
645
+ * MEMO models a transition as `part def Transition` with `sourceState` and
646
+ * `targetState` as **Strings** (§13.3) — and in the shipped content those
647
+ * strings hold display names ("Idle"), not element ids, so nothing can follow
648
+ * them to the states they name. The native form's source and target are real
649
+ * references, so they are resolved to element ids here.
650
+ *
651
+ * Both are still written into the attribute map under the same keys the part
652
+ * def declares. That is deliberate: it keeps every existing consumer of
653
+ * `sourceState` / `targetState` working while the two spellings coexist, and it
654
+ * is what makes the eventual migration a change of value (display name →
655
+ * element id) that a diff can show, rather than a change of shape that it
656
+ * cannot.
657
+ *
658
+ * An anonymous transition (`transition initial then off;`) declares no element.
659
+ * It is a control statement, not a named thing the model traces to.
660
+ */
661
+ function extractTransition(transition, filePath, packageName, config, elements, registry, registries) {
662
+ if (!transition.name)
663
+ return;
664
+ const attributes = {
665
+ ...extractAttributes(transition.body),
666
+ sourceState: transition.source,
667
+ targetState: transition.target,
668
+ };
669
+ if (transition.trigger)
670
+ attributes.trigger = transition.trigger;
671
+ if (transition.effect)
672
+ attributes.effectSummary = transition.effect;
673
+ const guard = renderGuardExpression(transition.guard);
674
+ if (guard !== undefined)
675
+ attributes.guardSummary = guard;
676
+ const { kindDef, resolvedKind } = transition.type
677
+ ? resolveKindDef(transition.type, config, registries)
678
+ : { kindDef: undefined, resolvedKind: 'Unknown' };
679
+ elements.set(transition.name, {
680
+ id: transition.name,
681
+ name: attributes.name || transition.name,
682
+ kind: resolvedKind,
683
+ construct: 'transition',
684
+ layer: kindDef?.layer || 'unknown',
685
+ file: filePath,
686
+ package: packageName || undefined,
687
+ attributes,
688
+ doc: extractDocComment(transition.body),
689
+ });
690
+ registry.registerElement(transition.name, packageName);
441
691
  }
442
692
  /**
443
693
  * Extract an ActionDefinition as a MemoElement with parameters.
@@ -562,8 +812,19 @@ function extractItemDefinition(itemDef, filePath, packageName, config, elements,
562
812
  registry.registerElement(id, packageName);
563
813
  }
564
814
  /**
565
- * Walk a definition body to extract port usages as owned port elements.
566
- * Sets owner on ports and ownedPorts on the definition.
815
+ * Walk an owner's body to extract port usages as owned port elements.
816
+ * Sets owner on ports and ownedPorts on the owner.
817
+ *
818
+ * The owner may be a definition (`part def Board { port p : DataPort; }`) or a
819
+ * USAGE — including another port. SysML v2 nests ports inside ports, which is
820
+ * how a panel cluster says "this one boundary feature carries these three
821
+ * connectors". Without the usage case a model that nests natively simply loses
822
+ * the children: they are declared, they resolve, and they never become
823
+ * elements, so they vanish from every diagram and every count. The only
824
+ * alternative spelling is a `Composes` connection from a port to a port, which
825
+ * CR-ONT-065 forbids — a whole in a decomposition has to be a part, an action
826
+ * or a state. So the native form was the only correct one, and it was the one
827
+ * that did not work.
567
828
  */
568
829
  function extractDefinitionPorts(def, filePath, packageName, config, elements, registry, registries) {
569
830
  const ownerId = def.name;
@@ -587,6 +848,37 @@ function extractDefinitionPorts(def, filePath, packageName, config, elements, re
587
848
  }
588
849
  }
589
850
  }
851
+ /**
852
+ * Walk a part usage's body to extract nested parts as owned elements, setting
853
+ * `owner` on each child.
854
+ *
855
+ * `extractUsage` flattens the `PartMember` shapes that are view metadata — a
856
+ * reference binding (`part viewpoint :> vp;`) and an untyped body
857
+ * (`part :>> selectionQuery { ... }`). A TYPED nested part
858
+ * (`part valve : Valve { ... }`) is real containment: without this it is
859
+ * declared, resolves, and never becomes an element — the same silent drop
860
+ * nested ports had before `extractDefinitionPorts` walked port usages. The
861
+ * `composes` edge that makes containment legible to consumers is synthesized
862
+ * from the `owner` chain in buildMemoModel.
863
+ */
864
+ function extractNestedParts(owner, filePath, packageName, config, elements, registry, registries) {
865
+ const ownerId = owner.name;
866
+ for (const member of owner.body || []) {
867
+ if (member.$type !== 'PartMember')
868
+ continue;
869
+ const pm = member;
870
+ // Only a TYPED nested part is containment. Untyped bodies (view
871
+ // selectionQuery) and reference bindings are view metadata, flattened
872
+ // to attributes in extractUsage — skip them here or a view's metadata
873
+ // becomes a phantom element and its selection query is lost.
874
+ if (!pm.type || pm.boundRef || !pm.name)
875
+ continue;
876
+ extractUsage({ name: pm.name, type: pm.type, body: pm.body }, 'part', filePath, packageName, config, elements, registry, registries);
877
+ const childEl = elements.get(pm.name);
878
+ if (childEl)
879
+ childEl.owner = ownerId;
880
+ }
881
+ }
590
882
  /**
591
883
  * Extract an ActionUsage, including nested actions, flows, and successions.
592
884
  * Supports both typed (action name : Type;) and composite (action name { ... }) forms.
@@ -693,12 +985,15 @@ function extractControlNode(node, filePath, packageName, elements, registry, par
693
985
  }
694
986
  function resolveConnection(conn, filePath, packageName, config, elements, relationships, registry, allElementIds) {
695
987
  const typeName = conn.type; // e.g. "Mitigates", "TraceTo"
696
- if (!typeName)
697
- return;
698
988
  if (!conn.source || !conn.target)
699
989
  return;
700
- // Normalize: "Mitigates" → "mitigates", "TraceTo" → "traceTo"
701
- const normalizedType = normalizeRelType(typeName);
990
+ // An untyped connection (`connection connect a to b;`, no `: SomeDef`) is
991
+ // the native binary connection with nothing further classifying it — the
992
+ // keyword IS the type, `connect`, the same as `flow`/`bind` (R10-S6,
993
+ // `ConnectsPhysically`'s migration). Before this it built to nothing at
994
+ // all: the same silent-drop shape as `BindingUsage`/`ExposeMember`.
995
+ // A typed connection normalizes its definition name: "Mitigates" → "mitigates".
996
+ const normalizedType = typeName ? normalizeRelType(typeName) : 'connect';
702
997
  // Resolve source and target using registry for cross-file resolution
703
998
  const sourceId = resolveEndpointId(conn.source.ref, packageName, registry, allElementIds);
704
999
  const targetId = resolveEndpointId(conn.target.ref, packageName, registry, allElementIds);
@@ -706,7 +1001,7 @@ function resolveConnection(conn, filePath, packageName, config, elements, relati
706
1001
  return;
707
1002
  // A named connection usage carries its own stable ID; anonymous ones fall
708
1003
  // back to a positional counter that shifts whenever the file changes.
709
- const attributes = extractAttributes(conn.body);
1004
+ const attributes = { ...extractAttributes(conn.body), ...extractRelationshipMetadata(conn.body) };
710
1005
  const rel = {
711
1006
  id: conn.name || `rel-${++relationshipCounter}`,
712
1007
  type: normalizedType,
@@ -715,6 +1010,9 @@ function resolveConnection(conn, filePath, packageName, config, elements, relati
715
1010
  targetId,
716
1011
  targetEnd: conn.target.endName,
717
1012
  file: filePath,
1013
+ sourceRange: conn.$cstNode
1014
+ ? { offset: conn.$cstNode.offset, length: conn.$cstNode.length }
1015
+ : undefined,
718
1016
  named: conn.name ? true : undefined,
719
1017
  attributes,
720
1018
  flowItem: attributes.transportedItem || attributes.flowItem || undefined,
@@ -730,33 +1028,97 @@ function resolveConnection(conn, filePath, packageName, config, elements, relati
730
1028
  }
731
1029
  relationships.push(rel);
732
1030
  }
1031
+ /**
1032
+ * Resolve a dotted endpoint path to the deepest element it names.
1033
+ *
1034
+ * A dotted endpoint has two shapes and they resolve differently.
1035
+ *
1036
+ * `receive.prescription` names an action and one of its PARAMETERS. The
1037
+ * parameter is not an element, so the endpoint is the action and the parameter
1038
+ * is the end label.
1039
+ *
1040
+ * `touchscreen.touchUsbIo` names a port and a port NESTED inside it, and
1041
+ * `coffeeMachine.serviceInterface.diagnostics` nests one level deeper again.
1042
+ * The nested port IS an element in its own right, so the endpoint is the child.
1043
+ * Reading it the first way silently retargets the edge at the parent, which is
1044
+ * not a resolution failure anyone sees: it produces a plausible relationship
1045
+ * pointing at the wrong feature, and it only surfaces later as a payload
1046
+ * mismatch on whichever edge happened to carry a different item from its
1047
+ * parent's.
1048
+ *
1049
+ * Walk the path through `owner` and take the deepest real element.
1050
+ */
1051
+ function resolveEndpoint(ref, elements) {
1052
+ const parts = ref.split('.');
1053
+ let id = parts[0];
1054
+ let i = 1;
1055
+ while (i < parts.length) {
1056
+ const child = elements.get(parts[i]);
1057
+ if (!child || child.owner !== id)
1058
+ break;
1059
+ id = parts[i];
1060
+ i++;
1061
+ }
1062
+ return { id, end: parts.slice(i).join('.') };
1063
+ }
1064
+ /**
1065
+ * Resolve a `bind outer = inner;` port delegation into a MemoRelationship.
1066
+ *
1067
+ * Unlike a flow this carries no item: delegation is identity, so there is
1068
+ * nothing travelling and nothing to label the edge with. Consumers that render
1069
+ * an itemless edge already suppress the label.
1070
+ */
1071
+ function resolveBinding(binding, filePath, _packageName, relationships, allElementIds, elements) {
1072
+ const sourceRef = binding.source;
1073
+ const targetRef = binding.target;
1074
+ if (!sourceRef || !targetRef)
1075
+ return;
1076
+ const { id: sourceId, end: sourceEnd } = resolveEndpoint(sourceRef, elements);
1077
+ const { id: targetId, end: targetEnd } = resolveEndpoint(targetRef, elements);
1078
+ if (!allElementIds.has(sourceId) || !allElementIds.has(targetId))
1079
+ return;
1080
+ relationships.push({
1081
+ id: `rel-${++relationshipCounter}`,
1082
+ type: 'bind',
1083
+ sourceId,
1084
+ sourceEnd,
1085
+ targetId,
1086
+ targetEnd,
1087
+ file: filePath,
1088
+ });
1089
+ }
733
1090
  /**
734
1091
  * Resolve a flow connection usage into a MemoRelationship.
735
1092
  * Flow endpoints use dot notation: "actionName.paramName"
736
1093
  */
737
- function resolveFlowConnection(flow, filePath, packageName, parentActionId, relationships, allElementIds) {
1094
+ function resolveFlowConnection(flow, filePath, packageName, parentActionId, relationships, allElementIds, elements) {
738
1095
  const sourceRef = flow.source?.ref;
739
1096
  const targetRef = flow.target?.ref;
740
1097
  if (!sourceRef || !targetRef)
741
1098
  return;
742
- // Parse dot notation: "receive.prescription" → actionId="receive", port="prescription"
743
- const sourceParts = sourceRef.split('.');
744
- const targetParts = targetRef.split('.');
745
- const sourceActionId = sourceParts[0];
746
- const sourcePort = sourceParts.length > 1 ? sourceParts.slice(1).join('.') : '';
747
- const targetActionId = targetParts[0];
748
- const targetPort = targetParts.length > 1 ? targetParts.slice(1).join('.') : '';
1099
+ const { id: sourceActionId, end: sourcePort } = resolveEndpoint(sourceRef, elements);
1100
+ const { id: targetActionId, end: targetPort } = resolveEndpoint(targetRef, elements);
749
1101
  // Only create relationship if both endpoints reference known elements
750
1102
  if (!allElementIds.has(sourceActionId) || !allElementIds.has(targetActionId))
751
1103
  return;
1104
+ // A NAMED flow carries a stable ID that survives rebuilds; an anonymous one
1105
+ // falls back to a positional counter that shifts whenever the file changes.
1106
+ // Same rule `resolveConnection` applies to connection usages, and the same
1107
+ // reason: authoring needs to inspect, delete, and deep-link a flow, and a
1108
+ // positional id makes every one of those wrong after the next edit.
752
1109
  const rel = {
753
- id: `rel-${++relationshipCounter}`,
1110
+ id: flow.name || `rel-${++relationshipCounter}`,
754
1111
  type: 'flow',
755
1112
  sourceId: sourceActionId,
756
1113
  sourceEnd: sourcePort,
757
1114
  targetId: targetActionId,
758
1115
  targetEnd: targetPort,
759
1116
  file: filePath,
1117
+ sourceRange: flow.$cstNode
1118
+ ? { offset: flow.$cstNode.offset, length: flow.$cstNode.length }
1119
+ : undefined,
1120
+ named: flow.name ? true : undefined,
1121
+ attributes: extractAttributes(flow.body),
760
1122
  flowItem: flow.itemType || undefined,
761
1123
  };
762
1124
  relationships.push(rel);
@@ -838,7 +1200,7 @@ function renderGuardExpression(expr) {
838
1200
  /**
839
1201
  * Resolve an allocate usage into a MemoRelationship and set allocatedTo on the element.
840
1202
  */
841
- function resolveAllocate(allocate, filePath, packageName, elements, relationships, registry, allElementIds) {
1203
+ function resolveAllocate(allocate, filePath, packageName, elements, relationships, registry, allElementIds, relationshipRegistry) {
842
1204
  const sourceRef = allocate.source;
843
1205
  const targetRef = allocate.target;
844
1206
  if (!sourceRef || !targetRef)
@@ -853,16 +1215,310 @@ function resolveAllocate(allocate, filePath, packageName, elements, relationship
853
1215
  if (sourceEl) {
854
1216
  sourceEl.allocatedTo = targetId;
855
1217
  }
856
- const rel = {
1218
+ // `allocate a to b;` and `connection : AllocatedTo connect …` are the same
1219
+ // statement, so they produce the same edge. They did not: the native form
1220
+ // emitted `allocateTo` with invented ends while the connection emitted
1221
+ // `allocatedTo` with the ontology's, and consumers keyed on one silently
1222
+ // missed the other — the DHF allocation queries ask for `allocatedTo`.
1223
+ //
1224
+ // A TYPED allocation — `allocation d : DeploysTo allocate m to node;` —
1225
+ // carries which allocation it is, and that distinction has to survive.
1226
+ // `DeploysTo` and `BuildsInto` are not `AllocatedTo` under another name:
1227
+ // they are separate edges the deployment views and the DHF read by name.
1228
+ // The `allocation def` naming them is what the typed form points at, so the
1229
+ // edge is its camelCase name whenever the registry knows it; an unknown or
1230
+ // absent type falls back to the base allocation, which is what bare
1231
+ // `allocate` has always meant.
1232
+ const typed = allocate.type ? pascalToCamelCase(allocate.type.split('::').pop()) : undefined;
1233
+ const edge = typed && relationshipRegistry?.has(typed) ? typed : 'allocatedTo';
1234
+ relationships.push(edge === 'allocatedTo'
1235
+ ? nativeTraceEdge('allocatedTo', sourceId, targetId, filePath)
1236
+ : typedAllocationEdge(edge, sourceId, targetId, filePath, relationshipRegistry));
1237
+ }
1238
+ /**
1239
+ * The trace edge for an `allocation x : SomeAllocationDef allocate a to b;`.
1240
+ *
1241
+ * Ends come from the registry entry for the named definition, so a typed
1242
+ * allocation and the equivalent `connection : SomeAllocationDef connect …`
1243
+ * produce byte-identical links — the same guarantee the three
1244
+ * LANGUAGE_NATIVE_RELATIONS rows carry, extended to every allocation def the
1245
+ * ontology declares rather than to a fixed list of three.
1246
+ */
1247
+ function typedAllocationEdge(type, sourceId, targetId, filePath, relationshipRegistry) {
1248
+ const ends = relationshipRegistry?.getRelType(type)?.ends ?? [];
1249
+ return {
857
1250
  id: `rel-${++relationshipCounter}`,
858
- type: 'allocateTo',
1251
+ type,
859
1252
  sourceId,
860
- sourceEnd: 'action',
1253
+ sourceEnd: ends[0]?.name ?? 'source',
861
1254
  targetId,
862
- targetEnd: 'part',
1255
+ targetEnd: ends[1]?.name ?? 'target',
863
1256
  file: filePath,
864
1257
  };
865
- relationships.push(rel);
1258
+ }
1259
+ /** Build the trace edge for a language-native relation, typed from the registry. */
1260
+ function nativeTraceEdge(name, sourceId, targetId, filePath) {
1261
+ const [sourceEnd, targetEnd] = LANGUAGE_NATIVE_RELATIONS[name].ends;
1262
+ return {
1263
+ id: `rel-${++relationshipCounter}`,
1264
+ type: name,
1265
+ sourceId,
1266
+ sourceEnd: sourceEnd.name,
1267
+ targetId,
1268
+ targetEnd: targetEnd.name,
1269
+ file: filePath,
1270
+ };
1271
+ }
1272
+ /**
1273
+ * The owner context of a native trace usage: the package it is declared in, and
1274
+ * the nearest enclosing named element.
1275
+ *
1276
+ * `verify` needs the second one — the case that does the verifying is the owner
1277
+ * of the member, never a written end — and `satisfy` needs the first to resolve
1278
+ * its references the way every other endpoint is resolved.
1279
+ */
1280
+ function nativeUsageContext(node) {
1281
+ const packages = [];
1282
+ let ownerId;
1283
+ for (let current = node.$container; current; current = current.$container) {
1284
+ if (current.$type === 'PackageDeclaration') {
1285
+ if (current.name)
1286
+ packages.unshift(current.name);
1287
+ continue;
1288
+ }
1289
+ // An `objective` is a MEMBERSHIP, not the element that does the
1290
+ // verifying — the standard writes `objective { verify r; }` inside the
1291
+ // case, and the case is the verifier. A named objective would otherwise
1292
+ // capture `ownerId` and the `verifiedBy` edge would point at a wrapper
1293
+ // that is not an extracted element, silently dropping the link.
1294
+ if (current.$type === 'ObjectiveMember')
1295
+ continue;
1296
+ if (ownerId === undefined && typeof current.name === 'string' && current.name) {
1297
+ ownerId = current.name;
1298
+ }
1299
+ }
1300
+ return { packageName: packages.join('::'), ownerId };
1301
+ }
1302
+ /**
1303
+ * Project a native usage that names ONE end and takes the other from its owner.
1304
+ *
1305
+ * `perform`, `include`, `exhibit` and `frame` all have this shape, and it is
1306
+ * `verify`'s shape with the ends swapped: `verify` names its source and owns
1307
+ * its target, these four own their source and name their target. Sharing the
1308
+ * resolution keeps the four from drifting apart, and keeps the rule that the
1309
+ * owner supplies the unwritten end stated in exactly one place.
1310
+ */
1311
+ function resolveOwnerRootedUsage(node, written, name, filePath, relationships, registry, allElementIds) {
1312
+ if (!written)
1313
+ return;
1314
+ const { packageName, ownerId } = nativeUsageContext(node);
1315
+ const sourceId = ownerId
1316
+ ? resolveEndpointId(ownerId, packageName, registry, allElementIds)
1317
+ : undefined;
1318
+ const targetId = resolveEndpointId(written, packageName, registry, allElementIds);
1319
+ if (!sourceId || !targetId)
1320
+ return;
1321
+ relationships.push(nativeTraceEdge(name, sourceId, targetId, filePath));
1322
+ }
1323
+ /**
1324
+ * Project a verification case's `subject` onto the `verifiedBy` edge.
1325
+ *
1326
+ * This is the standard's answer to verifying something that is not a
1327
+ * requirement: the verified element becomes the case's SUBJECT and the
1328
+ * requirement its `objective { verify … }`. 61 of MEMO's 99 `VerifiedBy` links
1329
+ * target a SystemFunction, a RiskControlMeasure, a SoftwareModule, a
1330
+ * BehaviorProperty or a TimingConstraint — none of which native `verify`
1331
+ * admits, because `verify` takes a RequirementUsage.
1332
+ *
1333
+ * The edge direction matches `VerifiedBy`: the subject is the source
1334
+ * (verificationTarget) and the owning case the target (verificationCase).
1335
+ *
1336
+ * A `subject` that declares a fresh feature rather than pointing at an existing
1337
+ * element — `subject testVehicle : Vehicle;` — binds nothing and contributes no
1338
+ * edge. Only the referring forms (`subject v :> vehicleTestConfig;`,
1339
+ * `subject v = vehicleTestConfig;`) name something already in the model. A
1340
+ * multi-valued subject is one parameter whose binding is a parenthesized list;
1341
+ * each bound element is one `verifiedBy` fact for the owning case.
1342
+ */
1343
+ function resolveSubject(subject, filePath, relationships, registry, allElementIds) {
1344
+ if (!subject.boundRefs?.length)
1345
+ return;
1346
+ const { packageName, ownerId } = nativeUsageContext(subject);
1347
+ const targetId = ownerId
1348
+ ? resolveEndpointId(ownerId, packageName, registry, allElementIds)
1349
+ : undefined;
1350
+ if (!targetId)
1351
+ return;
1352
+ for (const boundRef of subject.boundRefs) {
1353
+ const sourceId = resolveEndpointId(boundRef, packageName, registry, allElementIds);
1354
+ if (sourceId)
1355
+ relationships.push(nativeTraceEdge('verifiedBy', sourceId, targetId, filePath));
1356
+ }
1357
+ }
1358
+ /**
1359
+ * Project a native `actor` onto the `participatesIn` edge (R10-S6).
1360
+ *
1361
+ * Same shape as `resolveSubject`: only the REFERRING form —
1362
+ * `actor driver = existingPart;` — names something already in the model, so
1363
+ * only that form produces an edge. A fresh declaration (`actor x : Person;`,
1364
+ * no `boundRef`) introduces a locally-scoped actor the model does not yet
1365
+ * have an element for; collapsing `MemoRelationship`'s `Initiates` and
1366
+ * `ParticipatesIn` onto the single native `actor` keyword means the
1367
+ * initiating/participating distinction is no longer a typed relationship —
1368
+ * it lives in how the actor is named, the same way `Composes` gave up its
1369
+ * whole/part distinction to plain nesting.
1370
+ *
1371
+ * The owner is the TARGET and the written reference the SOURCE — `verify`'s
1372
+ * shape, not `perform`'s: the use case is what the actor participates IN.
1373
+ */
1374
+ function resolveActorParticipation(actor, filePath, relationships, registry, allElementIds) {
1375
+ if (!actor.boundRef)
1376
+ return;
1377
+ const { packageName, ownerId } = nativeUsageContext(actor);
1378
+ const sourceId = resolveEndpointId(actor.boundRef, packageName, registry, allElementIds);
1379
+ const targetId = ownerId ? resolveEndpointId(ownerId, packageName, registry, allElementIds) : undefined;
1380
+ if (!sourceId || !targetId)
1381
+ return;
1382
+ relationships.push(nativeTraceEdge('participatesIn', sourceId, targetId, filePath));
1383
+ }
1384
+ /**
1385
+ * Project every `dependency a to b;` onto the generic `dependency` edge, and
1386
+ * additionally onto `realizes` when it carries `#refinement` (R10-S6).
1387
+ *
1388
+ * The only native relation here that is not a connection. A Dependency takes n
1389
+ * clients and n suppliers rather than a source and a target, so one written
1390
+ * dependency can produce several edges — `dependency Schemata from s to a, b;`
1391
+ * is two statements sharing a name, and it lowers to two links, which is what
1392
+ * `Realizes` would have needed two connection usages to say.
1393
+ *
1394
+ * A dependency WITHOUT `#refinement` is an unclassified dependency: it means
1395
+ * strictly less than `Realizes` does, so it contributes only the generic
1396
+ * `dependency` edge, never `realizes` — inferring realization from a bare
1397
+ * dependency would put a claim in the traceability matrix that the model
1398
+ * never made. `#refinement` adds `realizes` on top; it does not replace the
1399
+ * generic edge, because a refinement dependency IS a dependency.
1400
+ *
1401
+ * Before R10-S6 a bare (non-`#refinement`) dependency built to nothing at
1402
+ * all — the same silent-drop shape as `BindingUsage`/`ExposeMember` — which
1403
+ * is what `ModuleUses`/`MonitorsChannel`'s migration to plain `dependency`
1404
+ * would otherwise have hit. Both collapse onto this one generic edge; native
1405
+ * `dependency` does not distinguish "uses a module" from "monitors a
1406
+ * channel" any more than `actor` distinguished an initiating actor from a
1407
+ * participating one, so that distinction now lives in the dependency's own
1408
+ * name/doc, not in a relationship type.
1409
+ */
1410
+ function resolveDependency(dependency, filePath, relationships, registry, allElementIds) {
1411
+ const isRefinement = (dependency.prefixMetadata ?? [])
1412
+ .some(annotation => annotation.type?.split('::').pop() === REFINEMENT_METADATA);
1413
+ const { packageName } = nativeUsageContext(dependency);
1414
+ for (const client of dependency.clients ?? []) {
1415
+ const sourceId = resolveEndpointId(client, packageName, registry, allElementIds);
1416
+ if (!sourceId)
1417
+ continue;
1418
+ for (const supplier of dependency.suppliers ?? []) {
1419
+ const targetId = resolveEndpointId(supplier, packageName, registry, allElementIds);
1420
+ if (!targetId)
1421
+ continue;
1422
+ relationships.push(nativeTraceEdge('dependency', sourceId, targetId, filePath));
1423
+ if (isRefinement)
1424
+ relationships.push(nativeTraceEdge('realizes', sourceId, targetId, filePath));
1425
+ }
1426
+ }
1427
+ }
1428
+ /**
1429
+ * Project every `satisfy` and `verify` in a document onto the trace edge its
1430
+ * MEMO connection-def equivalent produces.
1431
+ */
1432
+ function resolveNativeTraceUsages(model, filePath, relationships, registry, allElementIds) {
1433
+ for (const node of AstUtils.streamAllContents(model)) {
1434
+ if (isSatisfyRequirementUsage(node)) {
1435
+ resolveSatisfy(node, filePath, relationships, registry, allElementIds);
1436
+ }
1437
+ else if (isRequirementVerificationMember(node)) {
1438
+ resolveVerify(node, filePath, relationships, registry, allElementIds);
1439
+ }
1440
+ else if (isPerformActionUsage(node)) {
1441
+ resolveOwnerRootedUsage(node, node.performed, 'performs', filePath, relationships, registry, allElementIds);
1442
+ }
1443
+ else if (isIncludeUseCaseUsage(node)) {
1444
+ resolveOwnerRootedUsage(node, node.included, 'includesStep', filePath, relationships, registry, allElementIds);
1445
+ }
1446
+ else if (isExhibitStateUsage(node)) {
1447
+ resolveOwnerRootedUsage(node, node.exhibited, 'exhibitsMode', filePath, relationships, registry, allElementIds);
1448
+ }
1449
+ else if (isFramedConcernMember(node)) {
1450
+ resolveOwnerRootedUsage(node, node.framed, 'framesConcern', filePath, relationships, registry, allElementIds);
1451
+ }
1452
+ else if (isSubjectMember(node)) {
1453
+ resolveSubject(node, filePath, relationships, registry, allElementIds);
1454
+ }
1455
+ else if (isActorMember(node)) {
1456
+ resolveActorParticipation(node, filePath, relationships, registry, allElementIds);
1457
+ }
1458
+ else if (isDependency(node)) {
1459
+ resolveDependency(node, filePath, relationships, registry, allElementIds);
1460
+ }
1461
+ else if (isVariantMember(node)) {
1462
+ resolveVariant(node, filePath, relationships, registry, allElementIds);
1463
+ }
1464
+ }
1465
+ }
1466
+ /**
1467
+ * Project `variant x;` onto a navigable choice-point edge.
1468
+ *
1469
+ * A variation's alternatives are memberships rather than connection usages,
1470
+ * so they would otherwise disappear from the consumer model entirely. The
1471
+ * nearest owner is the variation point and the written member names the
1472
+ * selected configuration. Keeping this as a first-class edge lets DSMs and
1473
+ * views distinguish a real choice from four unrelated parts.
1474
+ */
1475
+ function resolveVariant(variant, filePath, relationships, registry, allElementIds) {
1476
+ const { packageName, ownerId } = nativeUsageContext(variant);
1477
+ const sourceId = ownerId
1478
+ ? resolveEndpointId(ownerId, packageName, registry, allElementIds)
1479
+ : undefined;
1480
+ const targetId = resolveEndpointId(variant.name, packageName, registry, allElementIds);
1481
+ if (!sourceId || !targetId)
1482
+ return;
1483
+ relationships.push({
1484
+ id: `rel-${++relationshipCounter}`,
1485
+ type: 'variant',
1486
+ sourceId,
1487
+ sourceEnd: 'variationPoint',
1488
+ targetId,
1489
+ targetEnd: 'variant',
1490
+ file: filePath,
1491
+ });
1492
+ }
1493
+ function resolveSatisfy(satisfy, filePath, relationships, registry, allElementIds) {
1494
+ // A negated claim — `assert not satisfy r by p;` — states that the
1495
+ // requirement is NOT satisfied. There is no MEMO connection for it, and
1496
+ // emitting a `satisfiedBy` edge would make a design review's rejection read
1497
+ // as coverage in the traceability matrix. It parses; it contributes no
1498
+ // trace edge until the model has somewhere honest to put it.
1499
+ if (satisfy.isNegated)
1500
+ return;
1501
+ const { packageName } = nativeUsageContext(satisfy);
1502
+ const sourceId = resolveEndpointId(satisfy.requirement, packageName, registry, allElementIds);
1503
+ // Without `by`, the satisfying subject is inherited from the enclosing
1504
+ // requirement rather than written. Nothing to bind, so no edge.
1505
+ const targetId = satisfy.satisfyingFeature
1506
+ ? resolveEndpointId(satisfy.satisfyingFeature, packageName, registry, allElementIds)
1507
+ : undefined;
1508
+ if (!sourceId || !targetId)
1509
+ return;
1510
+ relationships.push(nativeTraceEdge('satisfiedBy', sourceId, targetId, filePath));
1511
+ }
1512
+ function resolveVerify(verify, filePath, relationships, registry, allElementIds) {
1513
+ const { packageName, ownerId } = nativeUsageContext(verify);
1514
+ const sourceId = resolveEndpointId(verify.requirement, packageName, registry, allElementIds);
1515
+ // The verifying case is the owner. A `verify` whose owner is not an
1516
+ // extracted element — a bare `verification def`, say — has no second end
1517
+ // to bind, so it stays a parse fact and not a graph fact.
1518
+ const targetId = ownerId ? resolveEndpointId(ownerId, packageName, registry, allElementIds) : undefined;
1519
+ if (!sourceId || !targetId)
1520
+ return;
1521
+ relationships.push(nativeTraceEdge('verifiedBy', sourceId, targetId, filePath));
866
1522
  }
867
1523
  // ─── Helpers ────────────────────────────────────────────────────────────────
868
1524
  function extractAttributes(body) {
@@ -870,7 +1526,7 @@ function extractAttributes(body) {
870
1526
  return {};
871
1527
  const attrs = {};
872
1528
  for (const member of body) {
873
- if (member.$type === 'AttributeMember' || member.$type === 'RefMember') {
1529
+ if (member.$type === 'AttributeMember' || member.$type === 'RefMember' || member.$type === 'MetadataFeatureMember') {
874
1530
  const attr = member;
875
1531
  if (attr.value) {
876
1532
  attrs[attr.name] = extractAttributeValue(attr.value);
@@ -893,6 +1549,35 @@ function extractAttributes(body) {
893
1549
  }
894
1550
  return attrs;
895
1551
  }
1552
+ /**
1553
+ * Read the standard `Rationale`/`StatusInfo` metadata (ModelingMetadata.sysml)
1554
+ * off a connection usage's body into the same flat attribute keys the deleted
1555
+ * `MemoRelationship.rationale`/`.status` used to occupy (R10-S5, 2026-08-13).
1556
+ *
1557
+ * `extractAttributes` only walks `AttributeMember`/`RefMember`/
1558
+ * `MetadataFeatureMember`; a `MetadataApplication` in a usage body (the `@Foo
1559
+ * {...}` form) is invisible to it, so without this a `@Rationale`/`@StatusInfo`
1560
+ * annotation would parse and silently vanish from the model — the same
1561
+ * BindingUsage/ExposeMember shape this epic exists to stop repeating.
1562
+ */
1563
+ function extractRelationshipMetadata(body) {
1564
+ if (!body)
1565
+ return {};
1566
+ const attrs = {};
1567
+ for (const member of body) {
1568
+ if (member.$type !== 'MetadataApplication')
1569
+ continue;
1570
+ const metadataType = member.type?.split('::').pop();
1571
+ const fields = extractAttributes(member.body);
1572
+ if (metadataType === 'Rationale' && fields.text) {
1573
+ attrs.rationale = fields.text;
1574
+ }
1575
+ else if (metadataType === 'StatusInfo' && fields.status) {
1576
+ attrs.status = fields.status;
1577
+ }
1578
+ }
1579
+ return attrs;
1580
+ }
896
1581
  function extractAttributeValue(value) {
897
1582
  if (!value)
898
1583
  return '';
@@ -967,11 +1652,8 @@ ref, packageName, registry, allElementIds) {
967
1652
  return direct;
968
1653
  }
969
1654
  /**
970
- * Normalize relationship type name:
971
- * PascalCase → camelCase for matching against config.relationshipTypes[].name
972
- * "Mitigates" → "mitigates", "TraceTo" → "traceTo", "AllocateTo" → "allocateTo"
1655
+ * Relationship type names are normalized by the shared helper in `naming.ts`,
1656
+ * which is the single definition of the source-name → model-key rule.
973
1657
  */
974
- function normalizeRelType(name) {
975
- return name.charAt(0).toLowerCase() + name.slice(1);
976
- }
1658
+ const normalizeRelType = toModelType;
977
1659
  //# sourceMappingURL=builder.js.map