mxrb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.de-DE.md +108 -0
  4. data/README.md +172 -0
  5. data/README.pt-BR.md +109 -0
  6. data/bin/mxrb +1613 -0
  7. data/docs/README.md +10 -0
  8. data/docs/de-DE/README.md +24 -0
  9. data/docs/de-DE/architectural-patterns.md +26 -0
  10. data/docs/de-DE/architectural-standard.md +48 -0
  11. data/docs/de-DE/architecture.md +76 -0
  12. data/docs/de-DE/compiler.md +162 -0
  13. data/docs/de-DE/conventions.md +19 -0
  14. data/docs/de-DE/design-system.md +52 -0
  15. data/docs/de-DE/entity-dsl.md +53 -0
  16. data/docs/de-DE/native-runtime-quality-report.md +74 -0
  17. data/docs/de-DE/oql-sql.md +211 -0
  18. data/docs/de-DE/platform-api-integration.md +36 -0
  19. data/docs/de-DE/platform-operations.md +243 -0
  20. data/docs/de-DE/project-structure.md +32 -0
  21. data/docs/de-DE/ruby-first-roadmap.md +116 -0
  22. data/docs/de-DE/scaffolds.md +59 -0
  23. data/docs/de-DE/semantic-refactoring.md +56 -0
  24. data/docs/de-DE/team-server.md +33 -0
  25. data/docs/de-DE/validation-matrix.md +64 -0
  26. data/docs/de-DE/vetclinic-acceptance.md +24 -0
  27. data/docs/de-DE/writing.md +138 -0
  28. data/docs/en-US/README.md +24 -0
  29. data/docs/en-US/architectural-patterns.md +151 -0
  30. data/docs/en-US/architectural-standard.md +89 -0
  31. data/docs/en-US/architecture.md +170 -0
  32. data/docs/en-US/compiler.md +157 -0
  33. data/docs/en-US/conventions.md +82 -0
  34. data/docs/en-US/design-system.md +163 -0
  35. data/docs/en-US/entity-dsl.md +53 -0
  36. data/docs/en-US/native-runtime-quality-report.md +73 -0
  37. data/docs/en-US/oql-sql.md +201 -0
  38. data/docs/en-US/platform-api-integration.md +36 -0
  39. data/docs/en-US/platform-operations.md +244 -0
  40. data/docs/en-US/project-structure.md +49 -0
  41. data/docs/en-US/ruby-first-roadmap.md +186 -0
  42. data/docs/en-US/scaffolds.md +57 -0
  43. data/docs/en-US/semantic-refactoring.md +198 -0
  44. data/docs/en-US/team-server.md +32 -0
  45. data/docs/en-US/validation-matrix.md +257 -0
  46. data/docs/en-US/vetclinic-acceptance.md +23 -0
  47. data/docs/en-US/writing.md +655 -0
  48. data/docs/pt-BR/README.md +24 -0
  49. data/docs/pt-BR/architectural-patterns.md +25 -0
  50. data/docs/pt-BR/architectural-standard.md +533 -0
  51. data/docs/pt-BR/architecture.md +300 -0
  52. data/docs/pt-BR/compiler.md +162 -0
  53. data/docs/pt-BR/conventions.md +19 -0
  54. data/docs/pt-BR/design-system.md +65 -0
  55. data/docs/pt-BR/entity-dsl.md +116 -0
  56. data/docs/pt-BR/native-runtime-quality-report.md +73 -0
  57. data/docs/pt-BR/oql-sql.md +208 -0
  58. data/docs/pt-BR/platform-api-integration.md +36 -0
  59. data/docs/pt-BR/platform-operations.md +260 -0
  60. data/docs/pt-BR/project-structure.md +32 -0
  61. data/docs/pt-BR/ruby-first-roadmap.md +239 -0
  62. data/docs/pt-BR/scaffolds.md +104 -0
  63. data/docs/pt-BR/semantic-refactoring.md +55 -0
  64. data/docs/pt-BR/team-server.md +59 -0
  65. data/docs/pt-BR/validation-matrix.md +86 -0
  66. data/docs/pt-BR/vetclinic-acceptance.md +50 -0
  67. data/docs/pt-BR/writing.md +177 -0
  68. data/examples/sudoku_evaluation.rb +14 -0
  69. data/examples/sudoku_functional_test.rb +28 -0
  70. data/lib/mxrb/architecture/graph.rb +124 -0
  71. data/lib/mxrb/architecture/validator.rb +98 -0
  72. data/lib/mxrb/benchmark.rb +33 -0
  73. data/lib/mxrb/compare.rb +390 -0
  74. data/lib/mxrb/compiler/adapter.rb +96 -0
  75. data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
  76. data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
  77. data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
  78. data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
  79. data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
  80. data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
  81. data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
  82. data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
  83. data/lib/mxrb/compiler/constants_materializer.rb +111 -0
  84. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
  85. data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
  86. data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
  87. data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
  88. data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
  89. data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
  90. data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
  91. data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
  92. data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
  93. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
  94. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
  95. data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
  96. data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
  97. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
  98. data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
  99. data/lib/mxrb/compiler/mda.rb +99 -0
  100. data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
  101. data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
  102. data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
  103. data/lib/mxrb/compiler/model_package.rb +116 -0
  104. data/lib/mxrb/compiler/model_values.rb +67 -0
  105. data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
  106. data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
  107. data/lib/mxrb/compiler/packager.rb +100 -0
  108. data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
  109. data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
  110. data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
  111. data/lib/mxrb/compiler/portable_packager.rb +356 -0
  112. data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
  113. data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
  114. data/lib/mxrb/compiler/project_materializer.rb +139 -0
  115. data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
  116. data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
  117. data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
  118. data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
  119. data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
  120. data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
  121. data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
  122. data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
  123. data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
  124. data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
  125. data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
  126. data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
  127. data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
  128. data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
  129. data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
  130. data/lib/mxrb/compiler/security_materializer.rb +104 -0
  131. data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
  132. data/lib/mxrb/compiler/settings_materializer.rb +25 -0
  133. data/lib/mxrb/compiler/source_model.rb +128 -0
  134. data/lib/mxrb/compiler/system_model_seed.rb +88 -0
  135. data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
  136. data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
  137. data/lib/mxrb/compiler/translation_materializer.rb +95 -0
  138. data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
  139. data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
  140. data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
  141. data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
  142. data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
  143. data/lib/mxrb/doctor.rb +116 -0
  144. data/lib/mxrb/dsl/builder.rb +1636 -0
  145. data/lib/mxrb/errors.rb +18 -0
  146. data/lib/mxrb/evaluation.rb +131 -0
  147. data/lib/mxrb/exporter.rb +1716 -0
  148. data/lib/mxrb/frontend/migrator.rb +775 -0
  149. data/lib/mxrb/functional.rb +307 -0
  150. data/lib/mxrb/github/annotator.rb +161 -0
  151. data/lib/mxrb/initializer.rb +257 -0
  152. data/lib/mxrb/integrity/validator.rb +136 -0
  153. data/lib/mxrb/io/bson_codec.rb +145 -0
  154. data/lib/mxrb/io/mpr_file.rb +558 -0
  155. data/lib/mxrb/io/mxunit_codec.rb +42 -0
  156. data/lib/mxrb/marketplace.rb +380 -0
  157. data/lib/mxrb/model/association.rb +98 -0
  158. data/lib/mxrb/model/attribute.rb +100 -0
  159. data/lib/mxrb/model/connector.rb +17 -0
  160. data/lib/mxrb/model/design_materializer.rb +133 -0
  161. data/lib/mxrb/model/design_migration.rb +126 -0
  162. data/lib/mxrb/model/design_system.rb +111 -0
  163. data/lib/mxrb/model/domain_model.rb +51 -0
  164. data/lib/mxrb/model/entity.rb +180 -0
  165. data/lib/mxrb/model/menu.rb +38 -0
  166. data/lib/mxrb/model/microflow.rb +100 -0
  167. data/lib/mxrb/model/module.rb +142 -0
  168. data/lib/mxrb/model/navigation.rb +127 -0
  169. data/lib/mxrb/model/page.rb +410 -0
  170. data/lib/mxrb/model/project.rb +187 -0
  171. data/lib/mxrb/model/unit.rb +48 -0
  172. data/lib/mxrb/module_initializer.rb +75 -0
  173. data/lib/mxrb/official_marketplace/content_api.rb +292 -0
  174. data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
  175. data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
  176. data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
  177. data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
  178. data/lib/mxrb/official_marketplace.rb +720 -0
  179. data/lib/mxrb/oql/analyzer.rb +155 -0
  180. data/lib/mxrb/oql/index_advisor.rb +85 -0
  181. data/lib/mxrb/oql/plan_analyzer.rb +185 -0
  182. data/lib/mxrb/oql/server.rb +132 -0
  183. data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
  184. data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
  185. data/lib/mxrb/oql/workload_analyzer.rb +175 -0
  186. data/lib/mxrb/oql/workload_baseline.rb +60 -0
  187. data/lib/mxrb/oql.rb +424 -0
  188. data/lib/mxrb/project_lifecycle.rb +76 -0
  189. data/lib/mxrb/protocols/plan.rb +86 -0
  190. data/lib/mxrb/protocols.rb +123 -0
  191. data/lib/mxrb/runtime/database_workspace.rb +677 -0
  192. data/lib/mxrb/runtime/docker_executor.rb +41 -0
  193. data/lib/mxrb/runtime/docker_workspace.rb +29 -0
  194. data/lib/mxrb/runtime/executor.rb +169 -0
  195. data/lib/mxrb/runtime/java_locator.rb +26 -0
  196. data/lib/mxrb/runtime/native.rb +474 -0
  197. data/lib/mxrb/runtime/sql_server_database.rb +146 -0
  198. data/lib/mxrb/runtime/toolchain.rb +82 -0
  199. data/lib/mxrb/scaffold/cli.rb +113 -0
  200. data/lib/mxrb/scaffold/generator.rb +246 -0
  201. data/lib/mxrb/scaffold/help.rb +80 -0
  202. data/lib/mxrb/scaffold/page_templates.rb +54 -0
  203. data/lib/mxrb/scaffold/recipes.rb +198 -0
  204. data/lib/mxrb/scaffold/registry.rb +82 -0
  205. data/lib/mxrb/scaffold/templates.rb +723 -0
  206. data/lib/mxrb/scaffold/transaction.rb +84 -0
  207. data/lib/mxrb/schema/tables.rb +55 -0
  208. data/lib/mxrb/semantic/analyzer.rb +473 -0
  209. data/lib/mxrb/semantic/batch_plan.rb +68 -0
  210. data/lib/mxrb/semantic/domain_mutator.rb +419 -0
  211. data/lib/mxrb/semantic/embedder.rb +51 -0
  212. data/lib/mxrb/semantic/extractor.rb +290 -0
  213. data/lib/mxrb/semantic/index.rb +623 -0
  214. data/lib/mxrb/semantic/inliner.rb +216 -0
  215. data/lib/mxrb/semantic/mover.rb +153 -0
  216. data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
  217. data/lib/mxrb/semantic/remover.rb +70 -0
  218. data/lib/mxrb/semantic/renamer.rb +211 -0
  219. data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
  220. data/lib/mxrb/semantic/vec_store.rb +75 -0
  221. data/lib/mxrb/team_server.rb +390 -0
  222. data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
  223. data/lib/mxrb/templates/project/11.12.1.json +32 -0
  224. data/lib/mxrb/templates/project/6.10.8.json +50 -0
  225. data/lib/mxrb/templates/project/7.17.0.json +50 -0
  226. data/lib/mxrb/templates/project/7.5.0.json +50 -0
  227. data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
  228. data/lib/mxrb/version.rb +5 -0
  229. data/lib/mxrb/widget_package.rb +338 -0
  230. data/lib/mxrb/widget_synchronizer.rb +31 -0
  231. data/lib/mxrb/writer.rb +3775 -0
  232. data/lib/mxrb.rb +188 -0
  233. data/marketplace/catalog.json +10 -0
  234. data/marketplace/modules/shared-kernel/domain/README.md +4 -0
  235. data/marketplace/modules/shared-kernel/module.rb +5 -0
  236. data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
  237. metadata +447 -0
@@ -0,0 +1,419 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Mxrb
6
+ module Semantic
7
+ # Plan that adds a new attribute to an existing entity.
8
+ class AddAttributePlan
9
+ attr_reader :changes
10
+
11
+ def initialize(project:, module_name:, entity_name:, entity_id:, domain_unit_id:, attribute_def:)
12
+ @project = project
13
+ @module_name = module_name
14
+ @entity_name = entity_name
15
+ @entity_id = entity_id
16
+ @domain_unit_id = domain_unit_id
17
+ @attribute_def = attribute_def
18
+ @applied = false
19
+ @changes = ["add #{attribute_def[:name]} (#{attribute_def[:type]}) to " \
20
+ "#{module_name}.#{entity_name}"].freeze
21
+ end
22
+
23
+ def empty? = false
24
+ def applied? = @applied
25
+
26
+ def apply!
27
+ raise ArgumentError, "plan was already applied" if @applied
28
+
29
+ @project.mpr.transaction do
30
+ raw = @project.raw_unit(@domain_unit_id)
31
+ raise ArgumentError, "domain model unit not found" unless raw
32
+
33
+ doc = @project.parse_bson(raw)
34
+ entities = DomainMutator.extract_entities(doc)
35
+ entity_doc = entities.find { IO::BsonCodec.extract_id(_1["$ID"] || _1["\$ID"]) == @entity_id }
36
+ raise ArgumentError, "entity #{@entity_name.inspect} not found in domain model" unless entity_doc
37
+
38
+ attrs = DomainMutator.extract_attributes(entity_doc)
39
+ existing = attrs.map { (_1["name"] || _1["Name"]).to_s }
40
+ raise ArgumentError, "attribute #{@attribute_def[:name]} already exists on #{@entity_name}" \
41
+ if existing.include?(@attribute_def[:name].to_s)
42
+
43
+ new_attr = Model::Attribute.new
44
+ new_attr.id = SecureRandom.uuid
45
+ new_attr.name = @attribute_def[:name].to_s
46
+ new_attr.documentation = @attribute_def.fetch(:documentation, "").to_s
47
+ new_attr.type = @attribute_def.fetch(:type, :string).to_sym
48
+ new_attr.default_value = @attribute_def[:default]&.to_s || ""
49
+ new_attr.data_storage_guid = SecureRandom.uuid
50
+ new_attr.export_level = "Hidden"
51
+
52
+ DomainMutator.put_attributes(entity_doc, attrs + [new_attr.to_bson])
53
+ DomainMutator.put_entity(doc, @entity_id, entity_doc)
54
+ @project.mpr.update_unit(@domain_unit_id, doc)
55
+ end
56
+
57
+ @project.refresh!
58
+ @applied = true
59
+ self
60
+ end
61
+ end
62
+
63
+ # Plan that removes an attribute from an entity, with incoming reference check.
64
+ class RemoveAttributePlan
65
+ attr_reader :attribute_name, :incoming, :changes
66
+
67
+ def initialize(project:, module_name:, entity_name:, entity_id:, domain_unit_id:,
68
+ attribute_name:, incoming:)
69
+ @project = project
70
+ @module_name = module_name
71
+ @entity_name = entity_name
72
+ @entity_id = entity_id
73
+ @domain_unit_id = domain_unit_id
74
+ @attribute_name = attribute_name
75
+ @incoming = incoming.freeze
76
+ @applied = false
77
+ @changes = ["remove #{module_name}.#{entity_name}/#{attribute_name}"].freeze
78
+ end
79
+
80
+ def safe? = @incoming.empty?
81
+ def empty? = false
82
+ def applied? = @applied
83
+
84
+ def apply!
85
+ raise ArgumentError, "plan was already applied" if @applied
86
+ unless safe?
87
+ raise ArgumentError,
88
+ "cannot remove #{@module_name}.#{@entity_name}/#{@attribute_name}: " \
89
+ "#{@incoming.size} incoming reference(s)"
90
+ end
91
+
92
+ @project.mpr.transaction do
93
+ raw = @project.raw_unit(@domain_unit_id)
94
+ raise ArgumentError, "domain model unit not found" unless raw
95
+
96
+ doc = @project.parse_bson(raw)
97
+ entities = DomainMutator.extract_entities(doc)
98
+ entity_doc = entities.find { IO::BsonCodec.extract_id(_1["$ID"] || _1["\$ID"]) == @entity_id }
99
+ raise ArgumentError, "entity not found" unless entity_doc
100
+
101
+ attrs = DomainMutator.extract_attributes(entity_doc)
102
+ before_size = attrs.size
103
+ attrs.reject! { (_1["name"] || _1["Name"]).to_s == @attribute_name }
104
+ raise ArgumentError, "attribute #{@attribute_name.inspect} not found on #{@entity_name}" \
105
+ if attrs.size == before_size
106
+
107
+ DomainMutator.put_attributes(entity_doc, attrs)
108
+ DomainMutator.put_entity(doc, @entity_id, entity_doc)
109
+ @project.mpr.update_unit(@domain_unit_id, doc)
110
+ end
111
+
112
+ @project.refresh!
113
+ @applied = true
114
+ self
115
+ end
116
+ end
117
+
118
+ # Plan that changes an attribute's type, default value, or documentation.
119
+ class ChangeAttributePlan
120
+ attr_reader :attribute_name, :updates, :changes
121
+
122
+ def initialize(project:, module_name:, entity_name:, entity_id:, domain_unit_id:,
123
+ attribute_name:, updates:)
124
+ @project = project
125
+ @module_name = module_name
126
+ @entity_name = entity_name
127
+ @entity_id = entity_id
128
+ @domain_unit_id = domain_unit_id
129
+ @attribute_name = attribute_name
130
+ @updates = updates.freeze
131
+ @applied = false
132
+ desc = updates.map { |k, v| "#{k}: #{v}" }.join(", ")
133
+ @changes = ["change #{module_name}.#{entity_name}/#{attribute_name} (#{desc})"].freeze
134
+ end
135
+
136
+ def empty? = false
137
+ def applied? = @applied
138
+
139
+ def apply!
140
+ raise ArgumentError, "plan was already applied" if @applied
141
+
142
+ @project.mpr.transaction do
143
+ raw = @project.raw_unit(@domain_unit_id)
144
+ raise ArgumentError, "domain model unit not found" unless raw
145
+
146
+ doc = @project.parse_bson(raw)
147
+ entities = DomainMutator.extract_entities(doc)
148
+ entity_doc = entities.find { IO::BsonCodec.extract_id(_1["$ID"] || _1["\$ID"]) == @entity_id }
149
+ raise ArgumentError, "entity not found" unless entity_doc
150
+
151
+ attrs = DomainMutator.extract_attributes(entity_doc)
152
+ attr_doc = attrs.find { (_1["name"] || _1["Name"]).to_s == @attribute_name }
153
+ raise ArgumentError, "attribute #{@attribute_name.inspect} not found on #{@entity_name}" unless attr_doc
154
+
155
+ attr_model = Model::Attribute.from_bson(attr_doc)
156
+ attr_model.type = @updates[:type].to_sym if @updates.key?(:type)
157
+ attr_model.default_value = @updates[:default].to_s if @updates.key?(:default)
158
+ attr_model.documentation = @updates[:documentation].to_s if @updates.key?(:documentation)
159
+
160
+ new_attrs = attrs.map { (_1["name"] || _1["Name"]).to_s == @attribute_name ? attr_model.to_bson : _1 }
161
+ DomainMutator.put_attributes(entity_doc, new_attrs)
162
+ DomainMutator.put_entity(doc, @entity_id, entity_doc)
163
+ @project.mpr.update_unit(@domain_unit_id, doc)
164
+ end
165
+
166
+ @project.refresh!
167
+ @applied = true
168
+ self
169
+ end
170
+ end
171
+
172
+ # Plan that adds a new entity to a module's domain model.
173
+ class AddEntityPlan
174
+ attr_reader :entity_def, :module_name, :changes
175
+
176
+ def initialize(project:, module_name:, domain_unit_id:, entity_def:)
177
+ @project = project
178
+ @module_name = module_name
179
+ @domain_unit_id = domain_unit_id
180
+ @entity_def = entity_def
181
+ @applied = false
182
+ @changes = ["add entity #{module_name}.#{entity_def[:name]}"].freeze
183
+ end
184
+
185
+ def empty? = false
186
+ def applied? = @applied
187
+
188
+ def apply!
189
+ raise ArgumentError, "plan was already applied" if @applied
190
+
191
+ @project.mpr.transaction do
192
+ raw = @project.raw_unit(@domain_unit_id)
193
+ raise ArgumentError, "domain model unit not found" unless raw
194
+
195
+ doc = @project.parse_bson(raw)
196
+ entities = DomainMutator.extract_entities(doc)
197
+ existing_names = entities.map { (_1["name"] || _1["Name"]).to_s }
198
+ raise ArgumentError, "entity #{@entity_def[:name]} already exists in #{@module_name}" \
199
+ if existing_names.include?(@entity_def[:name].to_s)
200
+
201
+ entity = Model::Entity.new
202
+ entity.id = SecureRandom.uuid
203
+ entity.name = @entity_def[:name].to_s
204
+ entity.qualified_name = "#{@module_name}.#{@entity_def[:name]}"
205
+ entity.documentation = @entity_def.fetch(:documentation, "").to_s
206
+ entity.data_storage_guid = SecureRandom.uuid
207
+ entity.export_level = "Hidden"
208
+ entity.persistable = !@entity_def[:non_persistent]
209
+ entity.location = { x: (entities.size % 4) * 220, y: (entities.size / 4) * 160 }
210
+ entity.access_rules = []
211
+
212
+ attrs = Array(@entity_def[:attributes]).map.with_index do |attr_def, i|
213
+ a = Model::Attribute.new
214
+ a.id = SecureRandom.uuid
215
+ a.name = attr_def[:name].to_s
216
+ a.documentation = attr_def.fetch(:documentation, "").to_s
217
+ a.type = attr_def.fetch(:type, :string).to_sym
218
+ a.default_value = attr_def[:default]&.to_s || ""
219
+ a.data_storage_guid = SecureRandom.uuid
220
+ a.export_level = "Hidden"
221
+ a
222
+ end
223
+ entity.instance_variable_set(:@attributes, attrs)
224
+
225
+ entities_key = DomainMutator.entities_key(doc)
226
+ doc[entities_key] = IO::BsonCodec.build_array(entities + [entity.to_bson])
227
+ @project.mpr.update_unit(@domain_unit_id, doc)
228
+ end
229
+
230
+ @project.refresh!
231
+ @applied = true
232
+ self
233
+ end
234
+ end
235
+
236
+ # Plan that removes an entity (and all its embedded attributes) from the domain model.
237
+ class RemoveEntityPlan
238
+ attr_reader :entity_name, :incoming, :changes
239
+
240
+ def initialize(project:, module_name:, entity_name:, entity_id:, domain_unit_id:, incoming:)
241
+ @project = project
242
+ @module_name = module_name
243
+ @entity_name = entity_name
244
+ @entity_id = entity_id
245
+ @domain_unit_id = domain_unit_id
246
+ @incoming = incoming.freeze
247
+ @applied = false
248
+ @changes = ["remove entity #{module_name}.#{entity_name}"].freeze
249
+ end
250
+
251
+ def safe? = @incoming.empty?
252
+ def empty? = false
253
+ def applied? = @applied
254
+
255
+ def apply!
256
+ raise ArgumentError, "plan was already applied" if @applied
257
+ unless safe?
258
+ raise ArgumentError,
259
+ "cannot remove #{@module_name}.#{@entity_name}: " \
260
+ "#{@incoming.size} incoming reference(s)"
261
+ end
262
+
263
+ @project.mpr.transaction do
264
+ raw = @project.raw_unit(@domain_unit_id)
265
+ raise ArgumentError, "domain model unit not found" unless raw
266
+
267
+ doc = @project.parse_bson(raw)
268
+ entities = DomainMutator.extract_entities(doc)
269
+ before_size = entities.size
270
+ entities.reject! { IO::BsonCodec.extract_id(_1["$ID"] || _1["\$ID"]) == @entity_id }
271
+ raise ArgumentError, "entity #{@entity_name.inspect} not found" if entities.size == before_size
272
+
273
+ entities_key = DomainMutator.entities_key(doc)
274
+ doc[entities_key] = IO::BsonCodec.build_array(entities)
275
+ @project.mpr.update_unit(@domain_unit_id, doc)
276
+ end
277
+
278
+ @project.refresh!
279
+ @applied = true
280
+ self
281
+ end
282
+ end
283
+
284
+ # Factory for domain model mutation plans.
285
+ #
286
+ # Usage:
287
+ # project.plan_add_attribute("M.Product", name: :Price, type: :decimal)
288
+ # project.plan_remove_attribute("M.Product/Price")
289
+ # project.plan_change_attribute("M.Product/Price", type: :integer, default: "0")
290
+ # project.plan_add_entity("M", name: :Tag, attributes: [{name: :Label, type: :string}])
291
+ # project.plan_remove_entity("M.Product")
292
+ class DomainMutator
293
+ def initialize(project)
294
+ @project = project
295
+ end
296
+
297
+ def plan_add_attribute(entity_qname, name:, type: :string, default: nil, documentation: nil)
298
+ mod_name, entity_name, dm_id, entity_id = resolve_entity(entity_qname)
299
+ AddAttributePlan.new(
300
+ project: @project, module_name: mod_name, entity_name: entity_name,
301
+ entity_id: entity_id, domain_unit_id: dm_id,
302
+ attribute_def: { name: name.to_s, type: type, default: default, documentation: documentation.to_s }
303
+ )
304
+ end
305
+
306
+ def plan_remove_attribute(attribute_qname)
307
+ mod_name, entity_name, dm_id, entity_id, attr_name = resolve_attribute(attribute_qname)
308
+ # Attribute qualified names in the semantic index use dots: M.Entity.Attr
309
+ artifact = @project.find_artifact("#{mod_name}.#{entity_name}.#{attr_name}", kind: :attribute)
310
+ incoming = artifact ? @project.references_to(artifact) : []
311
+ RemoveAttributePlan.new(
312
+ project: @project, module_name: mod_name, entity_name: entity_name,
313
+ entity_id: entity_id, domain_unit_id: dm_id,
314
+ attribute_name: attr_name, incoming: incoming
315
+ )
316
+ end
317
+
318
+ def plan_change_attribute(attribute_qname, type: nil, default: nil, documentation: nil)
319
+ mod_name, entity_name, dm_id, entity_id, attr_name = resolve_attribute(attribute_qname)
320
+ updates = {}
321
+ updates[:type] = type if type
322
+ updates[:default] = default unless default.nil?
323
+ updates[:documentation] = documentation if documentation
324
+ raise ArgumentError, "plan_change_attribute: no changes specified" if updates.empty?
325
+
326
+ ChangeAttributePlan.new(
327
+ project: @project, module_name: mod_name, entity_name: entity_name,
328
+ entity_id: entity_id, domain_unit_id: dm_id,
329
+ attribute_name: attr_name, updates: updates
330
+ )
331
+ end
332
+
333
+ def plan_add_entity(module_qname, name:, attributes: [], documentation: nil, non_persistent: false)
334
+ mod_name, dm_id = resolve_module(module_qname)
335
+ AddEntityPlan.new(
336
+ project: @project, module_name: mod_name, domain_unit_id: dm_id,
337
+ entity_def: {
338
+ name: name.to_s, attributes: attributes,
339
+ documentation: documentation.to_s, non_persistent: non_persistent
340
+ }
341
+ )
342
+ end
343
+
344
+ def plan_remove_entity(entity_qname)
345
+ mod_name, entity_name, dm_id, entity_id = resolve_entity(entity_qname)
346
+ artifact = @project.find_artifact(entity_qname, kind: :entity)
347
+ incoming = artifact ? @project.references_to(artifact).reject { _1.source.qualified_name == entity_qname } : []
348
+ RemoveEntityPlan.new(
349
+ project: @project, module_name: mod_name, entity_name: entity_name,
350
+ entity_id: entity_id, domain_unit_id: dm_id, incoming: incoming
351
+ )
352
+ end
353
+
354
+ # ── BSON helpers used by plans ──────────────────────────────────────────
355
+
356
+ def self.extract_entities(doc)
357
+ IO::BsonCodec.parse_array(doc["entities"] || doc["Entities"])[:items]
358
+ end
359
+
360
+ def self.entities_key(doc)
361
+ doc.key?("entities") ? "entities" : "Entities"
362
+ end
363
+
364
+ def self.extract_attributes(entity_doc)
365
+ IO::BsonCodec.parse_array(entity_doc["attributes"] || entity_doc["Attributes"])[:items]
366
+ end
367
+
368
+ def self.put_attributes(entity_doc, attrs)
369
+ key = entity_doc.key?("attributes") ? "attributes" : "Attributes"
370
+ entity_doc[key] = IO::BsonCodec.build_array(attrs)
371
+ end
372
+
373
+ def self.put_entity(domain_doc, entity_id, updated_entity_doc)
374
+ key = entities_key(domain_doc)
375
+ entities = IO::BsonCodec.parse_array(domain_doc[key])[:items]
376
+ updated = entities.map do |e|
377
+ IO::BsonCodec.extract_id(e["$ID"] || e["\$ID"]) == entity_id ? updated_entity_doc : e
378
+ end
379
+ domain_doc[key] = IO::BsonCodec.build_array(updated)
380
+ end
381
+
382
+ private
383
+
384
+ def resolve_module(name)
385
+ mod = @project.modules.find { _1.name == name.to_s }
386
+ raise KeyError, "module #{name.inspect} not found" unless mod
387
+
388
+ dm = mod.domain_model
389
+ raise ArgumentError, "module #{name} has no domain model" unless dm
390
+
391
+ [mod.name, dm.id]
392
+ end
393
+
394
+ def resolve_entity(qname)
395
+ parts = qname.to_s.split(".", 2)
396
+ raise ArgumentError, "expected ModuleName.EntityName, got #{qname.inspect}" unless parts.size == 2
397
+
398
+ mod_name, entity_name = parts
399
+ mod = @project.modules.find { _1.name == mod_name }
400
+ raise KeyError, "module #{mod_name.inspect} not found" unless mod
401
+
402
+ dm = mod.domain_model
403
+ raise ArgumentError, "module #{mod_name} has no domain model" unless dm
404
+
405
+ entity = mod.entities.find { _1.name == entity_name }
406
+ raise KeyError, "entity #{qname.inspect} not found" unless entity
407
+
408
+ [mod_name, entity_name, dm.id, entity.id]
409
+ end
410
+
411
+ def resolve_attribute(qname)
412
+ entity_part, attr_name = qname.to_s.split("/", 2)
413
+ raise ArgumentError, "expected M.Entity/AttributeName, got #{qname.inspect}" unless attr_name
414
+
415
+ [*resolve_entity(entity_part), attr_name]
416
+ end
417
+ end
418
+ end
419
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tfidf_embedder'
4
+ require_relative 'onnx_embedder'
5
+
6
+ module Mxrb
7
+ module Semantic
8
+ RankedArtifact = Data.define(:artifact, :similarity, :distance)
9
+
10
+ # Builds optional embedding backends and ranks artifacts in pure Ruby.
11
+ module Embedder
12
+ module_function
13
+
14
+ def build(backend: :auto)
15
+ case backend
16
+ when :tfidf then TfidfEmbedder.new
17
+ when :onnx then OnnxEmbedder.new
18
+ when :auto then OnnxEmbedder.available? ? OnnxEmbedder.new : TfidfEmbedder.new
19
+ else raise ArgumentError, "unknown embedding backend: #{backend.inspect}"
20
+ end
21
+ end
22
+
23
+ def artifact_text(artifact)
24
+ metadata = artifact.metadata || {}
25
+ [
26
+ artifact.qualified_name, artifact.name, artifact.kind,
27
+ artifact.module_name, metadata[:documentation]
28
+ ].compact.join(' ')
29
+ end
30
+
31
+ def rank(artifacts, query, embedder:, limit:)
32
+ rank_with_distance(artifacts, query, embedder:, limit:).map(&:artifact).freeze
33
+ end
34
+
35
+ def rank_with_distance(artifacts, query, embedder:, limit:)
36
+ query_vector = embedder.embed(query.to_s)
37
+ scored = artifacts.map do |artifact|
38
+ similarity = dot(query_vector, embedder.embed(artifact_text(artifact)))
39
+ RankedArtifact.new(artifact, similarity, 1.0 - similarity)
40
+ end
41
+ scored.sort_by { [-_1.similarity, _1.artifact.qualified_name] }.first(limit).freeze
42
+ end
43
+
44
+ def dot(left, right)
45
+ raise ArgumentError, 'embedding dimensions do not match' unless left.size == right.size
46
+
47
+ left.each_index.sum { left[_1] * right[_1] }
48
+ end
49
+ end
50
+ end
51
+ end