ea 0.1.0 → 0.1.4

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,51 @@
1
+ # 28 - Extract XmlSanitizer (single-pass empty-element strip)
2
+
3
+ ## Status: DONE → SUPERSEDED (2026-07-01)
4
+
5
+ The XmlSanitizer was extracted into its own class on this branch
6
+ and used as a post-processing pass on the Transformer output. It was
7
+ then deleted when the upstream fix (xmi gem VALUE_MAP) made it
8
+ unnecessary.
9
+
10
+ ## History
11
+
12
+ ### Step 1: extracted into its own class
13
+ Originally `Transformer#strip_empty_elements` was a 12-line
14
+ method on the transformer that worked around the xmi gem's
15
+ round-trip-oriented `VALUE_MAP`. The workaround used a while-loop
16
+ with Nokogiri XPath to remove truly-empty elements.
17
+
18
+ Extracted to `lib/ea/transformers/qea_to_xmi/xml_sanitizer.rb`
19
+ as a focused class with a single-pass depth-first post-order walk.
20
+ Spec at `spec/ea/transformers/qea_to_xmi/xml_sanitizer_spec.rb`
21
+ (95 LOC, 11 examples).
22
+
23
+ ### Step 2: superseded by upstream VALUE_MAP fix
24
+ The xmi gem refactor (`refactor/owned-end-schema-gap`) changed
25
+ `Xmi::VALUE_MAP` to skip empty elements on serialization:
26
+
27
+ ```ruby
28
+ VALUE_MAP = {
29
+ from: { nil: :empty, empty: :empty, omitted: :empty },
30
+ to: { nil: :omitted, empty: :omitted, omitted: :omitted },
31
+ }
32
+ ```
33
+
34
+ After this change, the ea transformer emits no `<child/>` empty
35
+ elements at all — the post-processing pass was unnecessary.
36
+
37
+ ### Step 3: deleted
38
+ `lib/ea/transformers/qea_to_xmi/xml_sanitizer.rb` and its spec
39
+ were deleted. The transformer's `serialize` method now just calls
40
+ `build_root.to_xml(use_prefix: true)` with no sanitization.
41
+
42
+ ## Verification
43
+ - Full ea suite: 2005 examples, 0 failures, 37 pending
44
+ - Output contains zero truly-empty elements (no `<generalization/>`,
45
+ no `<ownedEnd/>` without attributes).
46
+ - The transformation pipeline is now: build model graph →
47
+ `to_xml(use_prefix: true)` → return. One pass, no Nokogiri
48
+ re-parse, no element-mutation pass.
49
+
50
+ ## Closes
51
+ TODO 21 §1 — "xmi gem empty-element rendering (architectural debt)".
@@ -0,0 +1,58 @@
1
+ # 29 - OCP registry for QEA classifier builders
2
+
3
+ ## Status: DONE (2026-07-01)
4
+
5
+ ## Problem
6
+ `Transformer#build_classifier` (transformer.rb:179-187) dispatches on
7
+ `obj.transformer_type` via a `case/when` statement:
8
+
9
+ ```ruby
10
+ case kind
11
+ when :class then build_class(obj)
12
+ when :enumeration then build_enumeration(obj)
13
+ when :data_type then build_data_type(obj)
14
+ when :instance then build_instance(obj)
15
+ end
16
+ ```
17
+
18
+ CLAUDE.md explicitly says "Registry pattern for OCP — new types are
19
+ added by registration, not by modifying `case/when`". With 4
20
+ branches the cost is low, but the project rule is the project rule.
21
+
22
+ TODO 21 defended the case statement by saying "polymorphism lives in
23
+ the xmi gem's xmi:type discriminator on PackagedElement". That is
24
+ true for *XMI rendering shape* (the xmi gem picks the right element
25
+ name + child set based on `xmi:type`). It is **not** true for the
26
+ decision of *which EA row maps to which builder* — that decision is
27
+ made by `EaObject#transformer_type` and dispatched here. Adding a
28
+ new builder (e.g., `:signal`, `:interface`) still requires modifying
29
+ this method.
30
+
31
+ ## Fix
32
+ Replace the case statement with a frozen hash of lambdas. Builders
33
+ are looked up by `transformer_type` symbol; unknown kinds return nil
34
+ (preserving current "skip" behaviour).
35
+
36
+ ```ruby
37
+ CLASSIFIER_BUILDERS = {
38
+ class: ->(t, o) { t.build_class(o) },
39
+ enumeration: ->(t, o) { t.build_enumeration(o) },
40
+ data_type: ->(t, o) { t.build_data_type(o) },
41
+ instance: ->(t, o) { t.build_instance(o) },
42
+ }.freeze
43
+
44
+ def build_classifier(obj)
45
+ kind = obj.transformer_type || obj.object_type&.downcase&.to_sym
46
+ CLASSIFIER_BUILDERS[kind]&.call(self, obj)
47
+ end
48
+ ```
49
+
50
+ Adding a new kind now means adding one entry to the constant, no
51
+ method body change.
52
+
53
+ ## Verification
54
+ - Behaviour unchanged: unknown kinds return nil (skipped downstream
55
+ via `compact`).
56
+ - Spec for `build_classifier` covers all 4 known kinds and the
57
+ unknown-kind fall-through.
58
+ - Project rule "Registry pattern for OCP" now satisfied.
@@ -0,0 +1,37 @@
1
+ # 30 - Replace ad-hoc Hash return with Struct in build_association_end
2
+
3
+ ## Status: DONE (2026-07-01)
4
+
5
+ ## Problem
6
+ `Transformer#build_association_end` (transformer.rb:326-344) returns
7
+ a two-key Hash:
8
+
9
+ ```ruby
10
+ { xmi_id: end_id, model: model }
11
+ ```
12
+
13
+ The caller (`build_association`) destructures it as
14
+ `dest_end[:xmi_id]`, `dest_end[:model]`. Ad-hoc Hash returns are
15
+ fragile — typos in keys (`[:xmii_id]`) fail silently as nil, and
16
+ the contract is invisible at the method signature.
17
+
18
+ ## Fix
19
+ Define a frozen Struct for the return type:
20
+
21
+ ```ruby
22
+ AssociationEnd = Struct.new(:xmi_id, :model)
23
+ ```
24
+
25
+ `build_association_end` returns `AssociationEnd.new(end_id, model)`.
26
+ Caller uses `dest_end.xmi_id`, `dest_end.model` — typos now raise
27
+ NoMethodError at the call site.
28
+
29
+ The Struct lives in the QeaToXmi namespace
30
+ (`Ea::Transformers::QeaToXmi::AssociationEnd`) so it can be referenced
31
+ in specs and reused by future wire-up code.
32
+
33
+ ## Verification
34
+ - `build_association` reads `dest_end.xmi_id` / `dest_end.model`
35
+ instead of hash keys.
36
+ - Spec covers `build_association_end` directly, asserting the Struct
37
+ shape and the synthesised IDs.
@@ -0,0 +1,27 @@
1
+ # 31 - Specs for IdAllocator
2
+
3
+ ## Status: DONE (2026-07-01)
4
+
5
+ ## Problem
6
+ `Ea::Transformers::QeaToXmi::IdAllocator` is a public class with two
7
+ public methods (`allocate`, `for_multiplicity` at the time of the
8
+ audit; after TODO 23 it has just `allocate`). It has counter state,
9
+ seed-based memoisation, and prefix-based formatting. None of this
10
+ behaviour is directly tested — it is only exercised transitively via
11
+ the Transformer specs.
12
+
13
+ ## Fix
14
+ New `spec/ea/transformers/qea_to_xmi/id_allocator_spec.rb`:
15
+
16
+ - `allocate` returns prefixed, zero-padded, monotonic IDs.
17
+ - Same seed → same ID (memoisation).
18
+ - Different seeds → different IDs.
19
+ - `nil` seed → still allocates (no memoisation).
20
+ - After TODO 25: parent GUID incorporation — `parent_guid` arg
21
+ produces the Sparx `EAID_LI000001_<guid_tail>` format.
22
+ - Multiple parents with the same counter values produce distinct
23
+ full IDs (GUID suffix disambiguates).
24
+
25
+ ## Verification
26
+ File exists, runs cleanly with `bundle exec rspec`, and covers the
27
+ public `allocate` API exhaustively.
@@ -0,0 +1,53 @@
1
+ # 32 - Phase 2 sentinel specs (track xmi gem gaps)
2
+
3
+ ## Status: ✅ DONE (2026-07-01) — sentinels flipped as xmi gem schema migrated
4
+
5
+ ## Problem
6
+ TODO 21 documented four attribute gaps the xmi gem didn't model.
7
+ The original spec suite did not assert the absence of these
8
+ attributes. When the xmi gem added support, nothing in the ea test
9
+ suite signaled that we should wire them up.
10
+
11
+ ## Original fix (sentinels)
12
+ Added sentinel specs that explicitly asserted these attributes were
13
+ not emitted, with comments pointing at TODO 21. When the xmi gem
14
+ added support, the first PR to wire the attribute up would need to
15
+ flip the assertion to positive — making the wiring visible in code
16
+ review.
17
+
18
+ ## Update after xmi gem schema migration
19
+ The xmi gem refactor branch `refactor/owned-end-schema-gap` landed
20
+ schema support for:
21
+ - `visibility` on Property / Operation / Parameter / OwnedEnd / Class
22
+ - `isAbstract` on packagedElement
23
+ - `aggregation` on OwnedEnd
24
+ - `classifier` on InstanceSpecification
25
+ - `upperValue`/`lowerValue` child models on OwnedEnd (TODO 26)
26
+ - `Slot`, `OpaqueExpression`, `InterfaceRealization` new models
27
+
28
+ The ea transformer was extended to wire up visibility, isAbstract,
29
+ aggregation (containment mapping), classifier (pdata1 mapping), and
30
+ the OwnedEnd child elements.
31
+
32
+ ## Current spec state
33
+ The original "Phase 2 gaps" sentinel block has split into two:
34
+
35
+ ### Phase 2 wiring (xmi gem schema migration landed)
36
+ Positive assertions for the four attributes now emitted:
37
+ - visibility on Property
38
+ - visibility on Operation
39
+ - isAbstract on packagedElement
40
+ - upperValue/lowerValue on ownedEnd
41
+
42
+ ### Phase 2 gaps still deferred
43
+ Negative assertions for two attributes the basic.qea fixture does
44
+ not exercise (no data to wire against):
45
+ - aggregation on ownedEnd (no composite/shared containment in fixture)
46
+ - classifier on InstanceSpecification (no pdata1 set in fixture)
47
+
48
+ When a fixture with relevant data is available, these flip to positive.
49
+
50
+ ## Verification
51
+ All qea_to_xmi specs pass (104 examples). Output now emits visibility
52
+ on all 102 attributes, isAbstract on all 65 classes, upperValue and
53
+ lowerValue on all 102 attributes + 80 association ends.
@@ -0,0 +1,30 @@
1
+ # 33 - normalize_lower was identity — fold into Cardinality module
2
+
3
+ ## Status: DONE (2026-07-01, folded into TODO 27)
4
+
5
+ ## Problem
6
+ `Transformer#normalize_lower` (transformer.rb:474-476) was:
7
+
8
+ ```ruby
9
+ def normalize_lower(raw)
10
+ raw.to_s
11
+ ```
12
+
13
+ Pure identity — does nothing. Either remove it (callers can `.to_s`
14
+ themselves) or implement actual normalisation matching how
15
+ `normalize_upper` handles the `*` token.
16
+
17
+ ## Fix
18
+ Folded into the Cardinality extraction (TODO 27). The new
19
+ `Cardinality.normalize_lower` returns the default `"0"` for empty
20
+ input, matching how real Sparx XMI represents unspecified lower
21
+ bounds (always emits `<lowerValue value="0"/>`, never empty).
22
+
23
+ This change also supports TODO 26 (upperValue/lowerValue count gap)
24
+ — always emitting `lowerValue` requires a sane default for the
25
+ "no bound specified" case.
26
+
27
+ ## Verification
28
+ - Old `normalize_lower` identity removed from transformer.
29
+ - `Cardinality.normalize_lower("")` returns `"0"`.
30
+ - `Cardinality.normalize_lower("1")` returns `"1"`.
@@ -0,0 +1,29 @@
1
+ # 34 - Document member-end order + harmonise RT prefix
2
+
3
+ ## Status: DONE (2026-07-01)
4
+
5
+ ## Problem
6
+ Two small clarity issues in `lib/ea/transformers/qea_to_xmi/transformer.rb`:
7
+
8
+ 1. **Implicit member-end order.** `build_association` (line 314-324)
9
+ emits `dest_end` first, then `src_end`. EA round-trip depends on
10
+ this ordering but no comment explains why. A future contributor
11
+ re-ordering for "tidiness" would silently break round-trip
12
+ fidelity.
13
+ 2. **Undocumented "RT" prefix.** `build_return_parameter` (line 400)
14
+ synthesises IDs with `prefix: "RT"`. The `IdAllocator` documents
15
+ well-known Sparx prefixes (LI, OE, SL, NL, DB) but not RT.
16
+
17
+ ## Fix
18
+
19
+ 1. Add a one-line comment at the member-end ordering site explaining
20
+ the Sparx convention. The ordering comes from how Sparx EA
21
+ serialises its `t_connector` rows: destination role first in
22
+ `<memberEnd>`, source role second.
23
+ 2. Add `RETURN_PARAMETER = "RT"` to IdAllocator's well-known prefix
24
+ list with a comment explaining what it stands for. Update
25
+ `build_return_parameter` to reference the constant.
26
+
27
+ ## Verification
28
+ - Diff shows the comment + constant addition; no behaviour change.
29
+ - Existing specs continue to pass.
@@ -0,0 +1,76 @@
1
+ # 35 - Walk t_object.RunState for instance specification slots
2
+
3
+ ## Status: ✅ DONE (2026-07-03)
4
+
5
+ ## Problem
6
+ `build_instance` originally emitted an empty `slot: []` array, which
7
+ silently dropped the run-state data that real Sparx XMI carries as
8
+ `<slot>` children of `<packagedElement type="uml:InstanceSpecification">`.
9
+
10
+ EA serialises run-state in `t_object.RunState` as a delimited string:
11
+
12
+ ```
13
+ @VAR;Variable=<name>;Value=<value>;Op=<op>;@ENDVAR;
14
+ ```
15
+
16
+ Multiple `@VAR` blocks concatenate directly. Each block maps to one
17
+ UML Slot.
18
+
19
+ ## Fix
20
+
21
+ ### RunState module
22
+ `lib/ea/transformers/qea_to_xmi/run_state.rb` is a pure-function
23
+ parser. Returns an array of `Binding` structs (`Struct.new(:variable,
24
+ :value, :op)`). The `Binding#body` method renders the Sparx wire form
25
+ (`Op==` + `Value=Alice` → `body="=Alice"`).
26
+
27
+ ### Transformer wiring
28
+ - `slots_for(obj)` walks `RunState.parse(obj.runstate)` and emits one
29
+ `Xmi::Uml::Slot` per binding.
30
+ - `build_slot(instance, binding)` constructs the Slot with:
31
+ - `xmi:id` from `IdAllocator.allocate(prefix: SLOT, ...)`
32
+ - `definingFeature` resolved by looking up the named attribute on
33
+ the instance's classifier (via `t_object.classifier`)
34
+ - one `OpaqueExpression` value carrying the body
35
+ - `build_slot_value` builds the OpaqueExpression with `xmi:id` from
36
+ `IdAllocator.allocate(prefix: OPAQUE_EXPRESSION, ...)`.
37
+ - `defining_feature_for(instance, binding)` looks up the classifier
38
+ via `obj.classifier.to_i` (NOT `pdata1` as originally documented —
39
+ the live `t_object.classifier` column holds the ea_object_id of
40
+ the classifier directly).
41
+
42
+ ### ID prefixes used
43
+ Both `SLOT` (`"SL"`) and `OPAQUE_EXPRESSION` (`"OE"`) are already on
44
+ `IdAllocator`'s well-known-prefix list — no constant additions.
45
+
46
+ ## Discovery: t_object.classifier column
47
+ The original TODO 35 draft assumed `pdata1` held the classifier ID.
48
+ Investigation during implementation showed:
49
+
50
+ - `pdata1`: always `nil` for `Object`-type rows in basic.qea.
51
+ - `classifier`: an Integer column holding the classifier's
52
+ `ea_object_id` directly. Zero when no classifier is set.
53
+
54
+ This matches what real Sparx XMI emits: InstanceSpecification rows
55
+ without a classifier have no `classifier="..."` attribute on the
56
+ `<packagedElement>`; rows with a classifier do.
57
+
58
+ ## Verification
59
+ - Output for basic.qea: 22 slots, 20 with `definingFeature` (the 2
60
+ without come from InstanceSpecifications that have no classifier).
61
+ This matches the reference `spec/fixtures/basic.xmi` exactly.
62
+ - Each slot carries one `<value>` child typed as
63
+ `uml:OpaqueExpression` with `body="=Value..."`.
64
+ - Slot IDs use the Sparx `EAID_SL<NN>__<guid>` format; OpaqueExpression
65
+ IDs use `EAID_OE<NN>__<guid>`.
66
+ - RunState module specs: 13 examples, 0 failures (pure-function
67
+ parser, edge cases covered).
68
+ - Slot emission specs in transformer_spec.rb: 4 new examples
69
+ asserting count, body shape, definingFeature presence, ID prefix.
70
+
71
+ ## Sentinel flipped
72
+ The "Phase 2 gaps still deferred" sentinel block had a negative
73
+ assertion for `classifier on InstanceSpecification`. With this
74
+ wiring landed, the spec flipped to a positive assertion. The
75
+ `aggregation on ownedEnd` sentinel remains negative (basic.qea
76
+ carries no composite/shared containment examples).
@@ -0,0 +1,50 @@
1
+ # 36 - Wire interfaceRealization for Interface-implementing classes
2
+
3
+ ## Status: DONE (2026-07-02)
4
+
5
+ ## Problem
6
+ Sparx XMI emits `<interfaceRealization>` as a child of the
7
+ implementing `<packagedElement type="uml:Class">`, not as a
8
+ package-level relationship. The ea transformer was not emitting
9
+ these at all — Realization connectors were silently dropped by
10
+ `package_level_relationships` (Realization is not in
11
+ `RELATIONSHIP_AT_PACKAGE_LEVEL`).
12
+
13
+ ## Fix
14
+
15
+ ### xmi gem
16
+ `Xmi::Uml::InterfaceRealization` model added in the xmi gem refactor
17
+ branch `refactor/owned-end-schema-gap`. `Xmi::Uml::PackagedElement`
18
+ declares `interface_realization` as a child-element collection.
19
+
20
+ ### ea transformer
21
+ - New `interface_realizations_for(obj)` method on
22
+ `Ea::Transformers::QeaToXmi::Transformer`. Walks the realization
23
+ connectors where this class is the source (client) and emits one
24
+ `InterfaceRealization` model per connector.
25
+ - New `build_interface_realization(conn)` builder. Wires
26
+ `client` (this class), `supplier` and `contract` (the target
27
+ Interface — supplier and contract are the same when the target is
28
+ an Interface; if EA's data model ever distinguishes, this is the
29
+ single place to change).
30
+ - `build_class` now passes `interface_realization:
31
+ interface_realizations_for(obj)`.
32
+ - New `realization_connectors(obj)` helper — a specialization of
33
+ `inheritance_connectors(obj, "Realization")` for readability.
34
+
35
+ ## Verification
36
+ - All 113 qea_to_xmi specs pass.
37
+ - `basic.qea` has 0 Realization connectors, so the output has 0
38
+ `<interfaceRealization>` elements. The path is exercised by the
39
+ xmi gem's `sparx-instance-specification.xmi` fixture (parser side)
40
+ and by structural round-trip — when the ea transformer is given an
41
+ EA database with Realization connectors, it will produce the
42
+ correct child elements.
43
+
44
+ ## Sentinel
45
+ The Phase 2 sentinel spec block in
46
+ `spec/ea/transformers/qea_to_xmi/transformer_spec.rb` keeps two
47
+ negative sentinels for cases `basic.qea` doesn't exercise
48
+ (aggregation on ownedEnd, classifier on InstanceSpecification).
49
+ Realization does not need a sentinel because the wiring path is
50
+ already type-checked end-to-end.
@@ -0,0 +1,36 @@
1
+ # 37 - Visibility.boolean_from_flag returns real Ruby booleans
2
+
3
+ ## Status: DONE (2026-07-02)
4
+
5
+ ## Problem
6
+ `Ea::Transformers::QeaToXmi::Visibility.boolean_from_flag` returned
7
+ the strings `"true"` / `"false"` for EA's `"1"` / `"0"` flags. The
8
+ xmi gem's `is_*` attributes (`is_abstract`, `is_static`,
9
+ `is_ordered`, `is_derived`, `is_unique`, `is_id`, `is_read_only`,
10
+ `is_query`) were initially typed as `:string` so this worked — but
11
+ the xmi gem refactor migrated them to `:boolean` per UML 2.5 type
12
+ semantics.
13
+
14
+ After the migration, the ea-side `boolean_from_flag` returning
15
+ strings caused silent type coercion (Ruby `"true" == true` is
16
+ false; lutaml-model was lenient but the type contract was wrong).
17
+
18
+ ## Fix
19
+ Return actual Ruby `true` / `false` (or `nil` when EA's field is
20
+ blank). Drop the ternary string coercion. Update the spec to assert
21
+ `be(true)` / `be(false)` instead of `eq("true")` / `eq("false")`.
22
+
23
+ ```ruby
24
+ def boolean_from_flag(raw)
25
+ return nil if raw.nil? || raw.to_s.strip.empty?
26
+
27
+ raw.to_s == "1"
28
+ end
29
+ ```
30
+
31
+ ## Verification
32
+ - visibility_spec.rb: 6 examples pass with the new boolean contract.
33
+ - Output `isAbstract="false"` count for basic.qea: 80 (one per
34
+ Class). All emit as lowercase XSD booleans per the xmi gem's
35
+ lutaml-model :boolean type.
36
+ - Full qea_to_xmi suite passes (113 examples).
@@ -0,0 +1,200 @@
1
+ # diagram_styles.yml - Configurable EA Diagram Styling
2
+ # This file defines the default styling for LutaML diagram generation
3
+
4
+ # Global defaults - applied when no other configuration matches
5
+ defaults:
6
+ colors:
7
+ background: "#FFFFFF"
8
+ default_fill: "#E0E0E0"
9
+ default_stroke: "#000000"
10
+ text: "#000000"
11
+
12
+ fonts:
13
+ default:
14
+ family: "Carlito, Arial, sans-serif"
15
+ size: 9
16
+ weight: 400
17
+ style: normal
18
+
19
+ class_name:
20
+ family: "Carlito, Arial, sans-serif"
21
+ size: 9
22
+ weight: 700 # bold
23
+ style: normal
24
+
25
+ stereotype:
26
+ family: "Carlito, Arial, sans-serif"
27
+ size: 9
28
+ weight: 400
29
+ style: normal
30
+
31
+ attribute:
32
+ family: "Carlito, Arial, sans-serif"
33
+ size: 9
34
+ weight: 400
35
+ style: normal
36
+
37
+ operation:
38
+ family: "Carlito, Arial, sans-serif"
39
+ size: 9
40
+ weight: 400
41
+ style: normal
42
+
43
+ box:
44
+ stroke_width: 2
45
+ stroke_linecap: round
46
+ stroke_linejoin: bevel
47
+ corner_radius: 0
48
+ padding: 5
49
+ fill_opacity: 1.0
50
+ stroke_opacity: 1.0
51
+
52
+ text:
53
+ visibility_public: "+"
54
+ visibility_private: "-"
55
+ visibility_protected: "#"
56
+ visibility_package: "~"
57
+ cardinality_format: "[%s]"
58
+ stereotype_format: "«%s»"
59
+
60
+ # Stereotype-based styling
61
+ # These apply when elements have specific stereotypes
62
+ stereotypes:
63
+ DataType:
64
+ colors:
65
+ fill: "#FFCCFF" # Pink for i-UR DataTypes
66
+ stroke: "#000000"
67
+ fonts:
68
+ class_name:
69
+ weight: 700
70
+ style: italic
71
+
72
+ FeatureType:
73
+ colors:
74
+ fill: "#FFFFCC" # Yellow for CityGML FeatureTypes
75
+ stroke: "#000000"
76
+
77
+ GMLType:
78
+ colors:
79
+ fill: "#CCFFCC" # Green for GML Types
80
+ stroke: "#000000"
81
+
82
+ Interface:
83
+ colors:
84
+ fill: "#FFFFEE"
85
+ stroke: "#000000"
86
+ fonts:
87
+ class_name:
88
+ style: italic
89
+
90
+ Enumeration:
91
+ colors:
92
+ fill: "#FFE6CC"
93
+ stroke: "#000000"
94
+
95
+ Abstract:
96
+ fonts:
97
+ class_name:
98
+ style: italic
99
+
100
+ # Package-based styling (optional)
101
+ # Supports wildcards: "CityGML::*" matches any package starting with "CityGML::"
102
+ packages:
103
+ "CityGML::*":
104
+ colors:
105
+ fill: "#FFFFCC"
106
+
107
+ "i-UR::*":
108
+ colors:
109
+ fill: "#FFCCFF"
110
+
111
+ "gml::*":
112
+ colors:
113
+ fill: "#CCFFCC"
114
+
115
+ # Class-specific overrides (highest priority after EA data)
116
+ # These take precedence over package and stereotype styling
117
+ classes: {}
118
+
119
+ # Connector styling
120
+ connectors:
121
+ generalization:
122
+ arrow:
123
+ type: hollow_triangle
124
+ size: 10
125
+ line:
126
+ stroke_width: 1
127
+ stroke: "#000000"
128
+ fill: none
129
+
130
+ association:
131
+ arrow:
132
+ type: open_arrow
133
+ size: 8
134
+ line:
135
+ stroke_width: 1
136
+ stroke: "#000000"
137
+ labels:
138
+ show_role_names: true
139
+ show_cardinality: true
140
+ font:
141
+ family: "Carlito, Arial, sans-serif"
142
+ size: 8
143
+
144
+ dependency:
145
+ arrow:
146
+ type: open_arrow
147
+ size: 8
148
+ line:
149
+ stroke_width: 1
150
+ stroke: "#000000"
151
+ stroke_dasharray: "5,5"
152
+
153
+ aggregation:
154
+ arrow:
155
+ type: diamond
156
+ size: 10
157
+ fill: white
158
+ line:
159
+ stroke_width: 1
160
+ stroke: "#000000"
161
+
162
+ composition:
163
+ arrow:
164
+ type: filled_diamond
165
+ size: 10
166
+ fill: black
167
+ line:
168
+ stroke_width: 1
169
+ stroke: "#000000"
170
+
171
+ realization:
172
+ arrow:
173
+ type: hollow_triangle
174
+ size: 10
175
+ line:
176
+ stroke_width: 1
177
+ stroke: "#000000"
178
+ stroke_dasharray: "5,5"
179
+
180
+ # Legend configuration
181
+ legend:
182
+ enabled: true
183
+ position: bottom_right
184
+ title: "Legend"
185
+ box:
186
+ fill: "#FFFFFF"
187
+ stroke: "#000000"
188
+ stroke_width: 1
189
+ padding: 10
190
+ font:
191
+ family: "Carlito, Arial, sans-serif"
192
+ size: 9
193
+ weight: 400
194
+ entries:
195
+ - label: "i-UR DataTypes"
196
+ color: "#FFCCFF"
197
+ - label: "CityGML FeatureTypes"
198
+ color: "#FFFFCC"
199
+ - label: "GML Types"
200
+ color: "#CCFFCC"