lex-agentic-integration 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 (236) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-integration.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/integration/boundary/client.rb +15 -0
  8. data/lib/legion/extensions/agentic/integration/boundary/helpers/boundary.rb +90 -0
  9. data/lib/legion/extensions/agentic/integration/boundary/helpers/boundary_engine.rb +123 -0
  10. data/lib/legion/extensions/agentic/integration/boundary/helpers/constants.rb +44 -0
  11. data/lib/legion/extensions/agentic/integration/boundary/runners/cognitive_boundary.rb +100 -0
  12. data/lib/legion/extensions/agentic/integration/boundary/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/integration/boundary.rb +19 -0
  14. data/lib/legion/extensions/agentic/integration/context/client.rb +26 -0
  15. data/lib/legion/extensions/agentic/integration/context/helpers/constants.rb +34 -0
  16. data/lib/legion/extensions/agentic/integration/context/helpers/context_manager.rb +150 -0
  17. data/lib/legion/extensions/agentic/integration/context/helpers/frame.rb +99 -0
  18. data/lib/legion/extensions/agentic/integration/context/runners/context.rb +94 -0
  19. data/lib/legion/extensions/agentic/integration/context/version.rb +13 -0
  20. data/lib/legion/extensions/agentic/integration/context.rb +19 -0
  21. data/lib/legion/extensions/agentic/integration/distributed_cognition/client.rb +19 -0
  22. data/lib/legion/extensions/agentic/integration/distributed_cognition/helpers/constants.rb +51 -0
  23. data/lib/legion/extensions/agentic/integration/distributed_cognition/helpers/distribution_engine.rb +159 -0
  24. data/lib/legion/extensions/agentic/integration/distributed_cognition/helpers/participant.rb +100 -0
  25. data/lib/legion/extensions/agentic/integration/distributed_cognition/runners/distributed_cognition.rb +107 -0
  26. data/lib/legion/extensions/agentic/integration/distributed_cognition/version.rb +13 -0
  27. data/lib/legion/extensions/agentic/integration/distributed_cognition.rb +19 -0
  28. data/lib/legion/extensions/agentic/integration/gestalt/client.rb +21 -0
  29. data/lib/legion/extensions/agentic/integration/gestalt/helpers/constants.rb +51 -0
  30. data/lib/legion/extensions/agentic/integration/gestalt/helpers/pattern.rb +90 -0
  31. data/lib/legion/extensions/agentic/integration/gestalt/helpers/pattern_store.rb +123 -0
  32. data/lib/legion/extensions/agentic/integration/gestalt/runners/gestalt.rb +82 -0
  33. data/lib/legion/extensions/agentic/integration/gestalt/version.rb +13 -0
  34. data/lib/legion/extensions/agentic/integration/gestalt.rb +19 -0
  35. data/lib/legion/extensions/agentic/integration/global_workspace/actors/competition.rb +45 -0
  36. data/lib/legion/extensions/agentic/integration/global_workspace/client.rb +29 -0
  37. data/lib/legion/extensions/agentic/integration/global_workspace/helpers/broadcast.rb +62 -0
  38. data/lib/legion/extensions/agentic/integration/global_workspace/helpers/competitor.rb +59 -0
  39. data/lib/legion/extensions/agentic/integration/global_workspace/helpers/constants.rb +65 -0
  40. data/lib/legion/extensions/agentic/integration/global_workspace/helpers/workspace.rb +188 -0
  41. data/lib/legion/extensions/agentic/integration/global_workspace/runners/global_workspace.rb +104 -0
  42. data/lib/legion/extensions/agentic/integration/global_workspace/version.rb +13 -0
  43. data/lib/legion/extensions/agentic/integration/global_workspace.rb +20 -0
  44. data/lib/legion/extensions/agentic/integration/integration/client.rb +19 -0
  45. data/lib/legion/extensions/agentic/integration/integration/helpers/constants.rb +62 -0
  46. data/lib/legion/extensions/agentic/integration/integration/helpers/integrated_representation.rb +106 -0
  47. data/lib/legion/extensions/agentic/integration/integration/helpers/integration_engine.rb +163 -0
  48. data/lib/legion/extensions/agentic/integration/integration/helpers/modal_signal.rb +62 -0
  49. data/lib/legion/extensions/agentic/integration/integration/runners/cognitive_integration.rb +105 -0
  50. data/lib/legion/extensions/agentic/integration/integration/version.rb +13 -0
  51. data/lib/legion/extensions/agentic/integration/integration.rb +20 -0
  52. data/lib/legion/extensions/agentic/integration/labyrinth/actors/thread_walker.rb +45 -0
  53. data/lib/legion/extensions/agentic/integration/labyrinth/client.rb +23 -0
  54. data/lib/legion/extensions/agentic/integration/labyrinth/helpers/constants.rb +39 -0
  55. data/lib/legion/extensions/agentic/integration/labyrinth/helpers/labyrinth.rb +138 -0
  56. data/lib/legion/extensions/agentic/integration/labyrinth/helpers/labyrinth_engine.rb +177 -0
  57. data/lib/legion/extensions/agentic/integration/labyrinth/helpers/node.rb +64 -0
  58. data/lib/legion/extensions/agentic/integration/labyrinth/runners/cognitive_labyrinth.rb +128 -0
  59. data/lib/legion/extensions/agentic/integration/labyrinth/version.rb +13 -0
  60. data/lib/legion/extensions/agentic/integration/labyrinth.rb +22 -0
  61. data/lib/legion/extensions/agentic/integration/map/actors/decay.rb +45 -0
  62. data/lib/legion/extensions/agentic/integration/map/client.rb +29 -0
  63. data/lib/legion/extensions/agentic/integration/map/helpers/cognitive_map_store.rb +179 -0
  64. data/lib/legion/extensions/agentic/integration/map/helpers/constants.rb +71 -0
  65. data/lib/legion/extensions/agentic/integration/map/helpers/graph_traversal.rb +124 -0
  66. data/lib/legion/extensions/agentic/integration/map/helpers/location.rb +71 -0
  67. data/lib/legion/extensions/agentic/integration/map/runners/cognitive_map.rb +95 -0
  68. data/lib/legion/extensions/agentic/integration/map/version.rb +13 -0
  69. data/lib/legion/extensions/agentic/integration/map.rb +20 -0
  70. data/lib/legion/extensions/agentic/integration/mosaic/client.rb +15 -0
  71. data/lib/legion/extensions/agentic/integration/mosaic/helpers/constants.rb +55 -0
  72. data/lib/legion/extensions/agentic/integration/mosaic/helpers/mosaic.rb +111 -0
  73. data/lib/legion/extensions/agentic/integration/mosaic/helpers/mosaic_engine.rb +124 -0
  74. data/lib/legion/extensions/agentic/integration/mosaic/helpers/tessera.rb +86 -0
  75. data/lib/legion/extensions/agentic/integration/mosaic/runners/cognitive_mosaic.rb +76 -0
  76. data/lib/legion/extensions/agentic/integration/mosaic/version.rb +13 -0
  77. data/lib/legion/extensions/agentic/integration/mosaic.rb +22 -0
  78. data/lib/legion/extensions/agentic/integration/mycelium/client.rb +19 -0
  79. data/lib/legion/extensions/agentic/integration/mycelium/helpers/constants.rb +52 -0
  80. data/lib/legion/extensions/agentic/integration/mycelium/helpers/fruiting_body.rb +50 -0
  81. data/lib/legion/extensions/agentic/integration/mycelium/helpers/hypha.rb +76 -0
  82. data/lib/legion/extensions/agentic/integration/mycelium/helpers/mycelial_node.rb +72 -0
  83. data/lib/legion/extensions/agentic/integration/mycelium/helpers/mycelium_engine.rb +151 -0
  84. data/lib/legion/extensions/agentic/integration/mycelium/runners/cognitive_mycelium.rb +73 -0
  85. data/lib/legion/extensions/agentic/integration/mycelium/version.rb +13 -0
  86. data/lib/legion/extensions/agentic/integration/mycelium.rb +23 -0
  87. data/lib/legion/extensions/agentic/integration/phenomenal_binding/client.rb +25 -0
  88. data/lib/legion/extensions/agentic/integration/phenomenal_binding/helpers/binding_engine.rb +156 -0
  89. data/lib/legion/extensions/agentic/integration/phenomenal_binding/helpers/binding_unit.rb +72 -0
  90. data/lib/legion/extensions/agentic/integration/phenomenal_binding/helpers/constants.rb +35 -0
  91. data/lib/legion/extensions/agentic/integration/phenomenal_binding/helpers/stream.rb +51 -0
  92. data/lib/legion/extensions/agentic/integration/phenomenal_binding/runners/phenomenal_binding.rb +110 -0
  93. data/lib/legion/extensions/agentic/integration/phenomenal_binding/version.rb +13 -0
  94. data/lib/legion/extensions/agentic/integration/phenomenal_binding.rb +20 -0
  95. data/lib/legion/extensions/agentic/integration/qualia/client.rb +19 -0
  96. data/lib/legion/extensions/agentic/integration/qualia/helpers/constants.rb +74 -0
  97. data/lib/legion/extensions/agentic/integration/qualia/helpers/quale.rb +103 -0
  98. data/lib/legion/extensions/agentic/integration/qualia/helpers/qualia_engine.rb +142 -0
  99. data/lib/legion/extensions/agentic/integration/qualia/runners/qualia.rb +67 -0
  100. data/lib/legion/extensions/agentic/integration/qualia/version.rb +13 -0
  101. data/lib/legion/extensions/agentic/integration/qualia.rb +19 -0
  102. data/lib/legion/extensions/agentic/integration/situation_model/client.rb +28 -0
  103. data/lib/legion/extensions/agentic/integration/situation_model/helpers/client.rb +23 -0
  104. data/lib/legion/extensions/agentic/integration/situation_model/helpers/constants.rb +40 -0
  105. data/lib/legion/extensions/agentic/integration/situation_model/helpers/situation_engine.rb +73 -0
  106. data/lib/legion/extensions/agentic/integration/situation_model/helpers/situation_event.rb +56 -0
  107. data/lib/legion/extensions/agentic/integration/situation_model/helpers/situation_model.rb +90 -0
  108. data/lib/legion/extensions/agentic/integration/situation_model/runners/situation_model.rb +99 -0
  109. data/lib/legion/extensions/agentic/integration/situation_model/version.rb +13 -0
  110. data/lib/legion/extensions/agentic/integration/situation_model.rb +21 -0
  111. data/lib/legion/extensions/agentic/integration/synthesis/client.rb +29 -0
  112. data/lib/legion/extensions/agentic/integration/synthesis/helpers/constants.rb +41 -0
  113. data/lib/legion/extensions/agentic/integration/synthesis/helpers/synthesis.rb +63 -0
  114. data/lib/legion/extensions/agentic/integration/synthesis/helpers/synthesis_engine.rb +213 -0
  115. data/lib/legion/extensions/agentic/integration/synthesis/helpers/synthesis_stream.rb +67 -0
  116. data/lib/legion/extensions/agentic/integration/synthesis/runners/cognitive_synthesis.rb +82 -0
  117. data/lib/legion/extensions/agentic/integration/synthesis/version.rb +13 -0
  118. data/lib/legion/extensions/agentic/integration/synthesis.rb +20 -0
  119. data/lib/legion/extensions/agentic/integration/tapestry/client.rb +15 -0
  120. data/lib/legion/extensions/agentic/integration/tapestry/helpers/constants.rb +46 -0
  121. data/lib/legion/extensions/agentic/integration/tapestry/helpers/loom_engine.rb +125 -0
  122. data/lib/legion/extensions/agentic/integration/tapestry/helpers/tapestry.rb +144 -0
  123. data/lib/legion/extensions/agentic/integration/tapestry/helpers/thread.rb +112 -0
  124. data/lib/legion/extensions/agentic/integration/tapestry/runners/cognitive_tapestry.rb +81 -0
  125. data/lib/legion/extensions/agentic/integration/tapestry/version.rb +13 -0
  126. data/lib/legion/extensions/agentic/integration/tapestry.rb +22 -0
  127. data/lib/legion/extensions/agentic/integration/tessellation/client.rb +19 -0
  128. data/lib/legion/extensions/agentic/integration/tessellation/helpers/constants.rb +72 -0
  129. data/lib/legion/extensions/agentic/integration/tessellation/helpers/mosaic.rb +99 -0
  130. data/lib/legion/extensions/agentic/integration/tessellation/helpers/tessellation_engine.rb +141 -0
  131. data/lib/legion/extensions/agentic/integration/tessellation/helpers/tile.rb +76 -0
  132. data/lib/legion/extensions/agentic/integration/tessellation/runners/cognitive_tessellation.rb +58 -0
  133. data/lib/legion/extensions/agentic/integration/tessellation/version.rb +13 -0
  134. data/lib/legion/extensions/agentic/integration/tessellation.rb +22 -0
  135. data/lib/legion/extensions/agentic/integration/version.rb +11 -0
  136. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/client.rb +17 -0
  137. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/cognitive_signal.rb +38 -0
  138. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/constants.rb +64 -0
  139. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/trend_window.rb +80 -0
  140. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/zeitgeist_engine.rb +157 -0
  141. data/lib/legion/extensions/agentic/integration/zeitgeist/runners/cognitive_zeitgeist.rb +99 -0
  142. data/lib/legion/extensions/agentic/integration/zeitgeist/version.rb +13 -0
  143. data/lib/legion/extensions/agentic/integration/zeitgeist.rb +22 -0
  144. data/lib/legion/extensions/agentic/integration.rb +34 -0
  145. data/spec/legion/extensions/agentic/integration/boundary/helpers/boundary_engine_spec.rb +165 -0
  146. data/spec/legion/extensions/agentic/integration/boundary/helpers/boundary_spec.rb +168 -0
  147. data/spec/legion/extensions/agentic/integration/boundary/helpers/constants_spec.rb +65 -0
  148. data/spec/legion/extensions/agentic/integration/boundary/runners/cognitive_boundary_spec.rb +111 -0
  149. data/spec/legion/extensions/agentic/integration/context/client_spec.rb +58 -0
  150. data/spec/legion/extensions/agentic/integration/context/helpers/constants_spec.rb +32 -0
  151. data/spec/legion/extensions/agentic/integration/context/helpers/context_manager_spec.rb +190 -0
  152. data/spec/legion/extensions/agentic/integration/context/helpers/frame_spec.rb +164 -0
  153. data/spec/legion/extensions/agentic/integration/context/runners/context_spec.rb +142 -0
  154. data/spec/legion/extensions/agentic/integration/distributed_cognition/client_spec.rb +21 -0
  155. data/spec/legion/extensions/agentic/integration/distributed_cognition/helpers/distribution_engine_spec.rb +149 -0
  156. data/spec/legion/extensions/agentic/integration/distributed_cognition/helpers/participant_spec.rb +116 -0
  157. data/spec/legion/extensions/agentic/integration/distributed_cognition/runners/distributed_cognition_spec.rb +102 -0
  158. data/spec/legion/extensions/agentic/integration/gestalt/client_spec.rb +53 -0
  159. data/spec/legion/extensions/agentic/integration/gestalt/helpers/constants_spec.rb +27 -0
  160. data/spec/legion/extensions/agentic/integration/gestalt/helpers/pattern_spec.rb +128 -0
  161. data/spec/legion/extensions/agentic/integration/gestalt/helpers/pattern_store_spec.rb +155 -0
  162. data/spec/legion/extensions/agentic/integration/gestalt/runners/gestalt_spec.rb +114 -0
  163. data/spec/legion/extensions/agentic/integration/global_workspace/client_spec.rb +49 -0
  164. data/spec/legion/extensions/agentic/integration/global_workspace/helpers/broadcast_spec.rb +83 -0
  165. data/spec/legion/extensions/agentic/integration/global_workspace/helpers/competitor_spec.rb +84 -0
  166. data/spec/legion/extensions/agentic/integration/global_workspace/helpers/workspace_spec.rb +174 -0
  167. data/spec/legion/extensions/agentic/integration/global_workspace/runners/global_workspace_spec.rb +118 -0
  168. data/spec/legion/extensions/agentic/integration/integration/cognitive_integration_spec.rb +7 -0
  169. data/spec/legion/extensions/agentic/integration/integration/helpers/integrated_representation_spec.rb +149 -0
  170. data/spec/legion/extensions/agentic/integration/integration/helpers/integration_engine_spec.rb +191 -0
  171. data/spec/legion/extensions/agentic/integration/integration/helpers/modal_signal_spec.rb +81 -0
  172. data/spec/legion/extensions/agentic/integration/labyrinth/client_spec.rb +122 -0
  173. data/spec/legion/extensions/agentic/integration/labyrinth/cognitive_labyrinth_spec.rb +19 -0
  174. data/spec/legion/extensions/agentic/integration/labyrinth/helpers/constants_spec.rb +72 -0
  175. data/spec/legion/extensions/agentic/integration/labyrinth/helpers/labyrinth_engine_spec.rb +222 -0
  176. data/spec/legion/extensions/agentic/integration/labyrinth/helpers/labyrinth_spec.rb +235 -0
  177. data/spec/legion/extensions/agentic/integration/labyrinth/helpers/node_spec.rb +157 -0
  178. data/spec/legion/extensions/agentic/integration/labyrinth/runners/cognitive_labyrinth_spec.rb +209 -0
  179. data/spec/legion/extensions/agentic/integration/map/client_spec.rb +89 -0
  180. data/spec/legion/extensions/agentic/integration/map/helpers/cognitive_map_store_spec.rb +321 -0
  181. data/spec/legion/extensions/agentic/integration/map/helpers/location_spec.rb +165 -0
  182. data/spec/legion/extensions/agentic/integration/map/runners/cognitive_map_spec.rb +190 -0
  183. data/spec/legion/extensions/agentic/integration/mosaic/client_spec.rb +16 -0
  184. data/spec/legion/extensions/agentic/integration/mosaic/helpers/constants_spec.rb +37 -0
  185. data/spec/legion/extensions/agentic/integration/mosaic/helpers/mosaic_engine_spec.rb +108 -0
  186. data/spec/legion/extensions/agentic/integration/mosaic/helpers/mosaic_spec.rb +120 -0
  187. data/spec/legion/extensions/agentic/integration/mosaic/helpers/tessera_spec.rb +92 -0
  188. data/spec/legion/extensions/agentic/integration/mosaic/runners/cognitive_mosaic_spec.rb +88 -0
  189. data/spec/legion/extensions/agentic/integration/mycelium/client_spec.rb +25 -0
  190. data/spec/legion/extensions/agentic/integration/mycelium/cognitive_mycelium_spec.rb +7 -0
  191. data/spec/legion/extensions/agentic/integration/mycelium/helpers/constants_spec.rb +28 -0
  192. data/spec/legion/extensions/agentic/integration/mycelium/helpers/fruiting_body_spec.rb +33 -0
  193. data/spec/legion/extensions/agentic/integration/mycelium/helpers/hypha_spec.rb +69 -0
  194. data/spec/legion/extensions/agentic/integration/mycelium/helpers/mycelial_node_spec.rb +79 -0
  195. data/spec/legion/extensions/agentic/integration/mycelium/helpers/mycelium_engine_spec.rb +136 -0
  196. data/spec/legion/extensions/agentic/integration/mycelium/runners/cognitive_mycelium_spec.rb +61 -0
  197. data/spec/legion/extensions/agentic/integration/phenomenal_binding/client_spec.rb +52 -0
  198. data/spec/legion/extensions/agentic/integration/phenomenal_binding/helpers/binding_engine_spec.rb +290 -0
  199. data/spec/legion/extensions/agentic/integration/phenomenal_binding/helpers/binding_unit_spec.rb +195 -0
  200. data/spec/legion/extensions/agentic/integration/phenomenal_binding/helpers/constants_spec.rb +92 -0
  201. data/spec/legion/extensions/agentic/integration/phenomenal_binding/helpers/stream_spec.rb +128 -0
  202. data/spec/legion/extensions/agentic/integration/phenomenal_binding/runners/phenomenal_binding_spec.rb +174 -0
  203. data/spec/legion/extensions/agentic/integration/qualia/client_spec.rb +21 -0
  204. data/spec/legion/extensions/agentic/integration/qualia/helpers/quale_spec.rb +168 -0
  205. data/spec/legion/extensions/agentic/integration/qualia/helpers/qualia_engine_spec.rb +174 -0
  206. data/spec/legion/extensions/agentic/integration/qualia/qualia_spec.rb +7 -0
  207. data/spec/legion/extensions/agentic/integration/qualia/runners_spec.rb +70 -0
  208. data/spec/legion/extensions/agentic/integration/situation_model/client_spec.rb +51 -0
  209. data/spec/legion/extensions/agentic/integration/situation_model/helpers/constants_spec.rb +56 -0
  210. data/spec/legion/extensions/agentic/integration/situation_model/helpers/situation_engine_spec.rb +203 -0
  211. data/spec/legion/extensions/agentic/integration/situation_model/helpers/situation_event_spec.rb +94 -0
  212. data/spec/legion/extensions/agentic/integration/situation_model/helpers/situation_model_spec.rb +235 -0
  213. data/spec/legion/extensions/agentic/integration/situation_model/runners/situation_model_spec.rb +204 -0
  214. data/spec/legion/extensions/agentic/integration/synthesis/client_spec.rb +26 -0
  215. data/spec/legion/extensions/agentic/integration/synthesis/helpers/constants_spec.rb +87 -0
  216. data/spec/legion/extensions/agentic/integration/synthesis/helpers/synthesis_engine_spec.rb +233 -0
  217. data/spec/legion/extensions/agentic/integration/synthesis/helpers/synthesis_spec.rb +101 -0
  218. data/spec/legion/extensions/agentic/integration/synthesis/helpers/synthesis_stream_spec.rb +116 -0
  219. data/spec/legion/extensions/agentic/integration/synthesis/runners/cognitive_synthesis_spec.rb +181 -0
  220. data/spec/legion/extensions/agentic/integration/tapestry/client_spec.rb +55 -0
  221. data/spec/legion/extensions/agentic/integration/tapestry/helpers/constants_spec.rb +88 -0
  222. data/spec/legion/extensions/agentic/integration/tapestry/helpers/loom_engine_spec.rb +225 -0
  223. data/spec/legion/extensions/agentic/integration/tapestry/helpers/tapestry_spec.rb +229 -0
  224. data/spec/legion/extensions/agentic/integration/tapestry/helpers/thread_spec.rb +211 -0
  225. data/spec/legion/extensions/agentic/integration/tapestry/runners/cognitive_tapestry_spec.rb +171 -0
  226. data/spec/legion/extensions/agentic/integration/tessellation/client_spec.rb +40 -0
  227. data/spec/legion/extensions/agentic/integration/tessellation/helpers/mosaic_spec.rb +99 -0
  228. data/spec/legion/extensions/agentic/integration/tessellation/helpers/tessellation_engine_spec.rb +145 -0
  229. data/spec/legion/extensions/agentic/integration/tessellation/helpers/tile_spec.rb +138 -0
  230. data/spec/legion/extensions/agentic/integration/zeitgeist/helpers/cognitive_signal_spec.rb +62 -0
  231. data/spec/legion/extensions/agentic/integration/zeitgeist/helpers/constants_spec.rb +73 -0
  232. data/spec/legion/extensions/agentic/integration/zeitgeist/helpers/trend_window_spec.rb +100 -0
  233. data/spec/legion/extensions/agentic/integration/zeitgeist/helpers/zeitgeist_engine_spec.rb +163 -0
  234. data/spec/legion/extensions/agentic/integration/zeitgeist/runners/cognitive_zeitgeist_spec.rb +165 -0
  235. data/spec/spec_helper.rb +46 -0
  236. metadata +320 -0
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Map::Helpers::Location do
4
+ subject(:location) { described_class.new(id: 'concept:ruby', domain: :programming) }
5
+
6
+ describe '#initialize' do
7
+ it 'sets id and domain' do
8
+ expect(location.id).to eq('concept:ruby')
9
+ expect(location.domain).to eq(:programming)
10
+ end
11
+
12
+ it 'starts with floor familiarity' do
13
+ expect(location.familiarity).to eq(Legion::Extensions::Agentic::Integration::Map::Helpers::Constants::FAMILIARITY_FLOOR)
14
+ end
15
+
16
+ it 'starts with zero visit count' do
17
+ expect(location.visit_count).to eq(0)
18
+ end
19
+
20
+ it 'starts with no neighbors' do
21
+ expect(location.neighbors).to be_empty
22
+ end
23
+
24
+ it 'starts with no last_visited' do
25
+ expect(location.last_visited).to be_nil
26
+ end
27
+ end
28
+
29
+ describe '#visit' do
30
+ it 'increments visit count' do
31
+ location.visit
32
+ expect(location.visit_count).to eq(1)
33
+ end
34
+
35
+ it 'sets last_visited' do
36
+ before = Time.now.utc
37
+ location.visit
38
+ expect(location.last_visited).to be >= before
39
+ end
40
+
41
+ it 'boosts familiarity' do
42
+ before = location.familiarity
43
+ location.visit
44
+ expect(location.familiarity).to be > before
45
+ end
46
+
47
+ it 'clamps familiarity at 1.0' do
48
+ 20.times { location.visit }
49
+ expect(location.familiarity).to be <= 1.0
50
+ end
51
+
52
+ it 'accumulates multiple visits' do
53
+ 3.times { location.visit }
54
+ expect(location.visit_count).to eq(3)
55
+ end
56
+ end
57
+
58
+ describe '#add_neighbor' do
59
+ it 'adds a neighbor with default distance' do
60
+ location.add_neighbor('concept:python')
61
+ expect(location.neighbors).to have_key('concept:python')
62
+ expect(location.neighbors['concept:python']).to eq(Legion::Extensions::Agentic::Integration::Map::Helpers::Constants::DEFAULT_DISTANCE)
63
+ end
64
+
65
+ it 'adds a neighbor with custom distance' do
66
+ location.add_neighbor('concept:java', distance: 2.5)
67
+ expect(location.neighbors['concept:java']).to eq(2.5)
68
+ end
69
+
70
+ it 'enforces distance floor' do
71
+ location.add_neighbor('concept:c', distance: 0.0)
72
+ expect(location.neighbors['concept:c']).to eq(Legion::Extensions::Agentic::Integration::Map::Helpers::Constants::DISTANCE_FLOOR)
73
+ end
74
+
75
+ it 'updates existing neighbor distance' do
76
+ location.add_neighbor('concept:python', distance: 1.0)
77
+ location.add_neighbor('concept:python', distance: 0.5)
78
+ expect(location.neighbors['concept:python']).to eq(0.5)
79
+ end
80
+
81
+ it 'respects MAX_EDGES_PER_LOCATION limit' do
82
+ max = Legion::Extensions::Agentic::Integration::Map::Helpers::Constants::MAX_EDGES_PER_LOCATION
83
+ (max + 5).times { |i| location.add_neighbor("neighbor_#{i}") }
84
+ expect(location.neighbors.size).to be <= max
85
+ end
86
+ end
87
+
88
+ describe '#remove_neighbor' do
89
+ it 'removes an existing neighbor' do
90
+ location.add_neighbor('concept:python')
91
+ location.remove_neighbor('concept:python')
92
+ expect(location.neighbors).not_to have_key('concept:python')
93
+ end
94
+
95
+ it 'is a no-op for unknown neighbors' do
96
+ expect { location.remove_neighbor('nonexistent') }.not_to raise_error
97
+ end
98
+ end
99
+
100
+ describe '#decay' do
101
+ it 'reduces familiarity' do
102
+ location.visit # raise above floor first
103
+ before = location.familiarity
104
+ location.decay
105
+ expect(location.familiarity).to be < before
106
+ end
107
+
108
+ it 'does not go below FAMILIARITY_FLOOR' do
109
+ 10.times { location.decay }
110
+ expect(location.familiarity).to be >= Legion::Extensions::Agentic::Integration::Map::Helpers::Constants::FAMILIARITY_FLOOR
111
+ end
112
+ end
113
+
114
+ describe '#faded?' do
115
+ it 'is true for a new location at floor familiarity with zero visits' do
116
+ expect(location.familiarity).to eq(Legion::Extensions::Agentic::Integration::Map::Helpers::Constants::FAMILIARITY_FLOOR)
117
+ expect(location.visit_count).to eq(0)
118
+ expect(location.faded?).to be true
119
+ end
120
+
121
+ it 'is false after visiting' do
122
+ location.visit
123
+ expect(location.faded?).to be false
124
+ end
125
+
126
+ it 'is false when familiarity is above floor even with zero visits' do
127
+ # familiarity raised manually above floor
128
+ 2.times { location.visit }
129
+ # simulate decay back but not below floor — visit_count > 0 so still false
130
+ 10.times { location.decay }
131
+ expect(location.faded?).to be false
132
+ end
133
+ end
134
+
135
+ describe '#label' do
136
+ it 'returns a familiarity level symbol' do
137
+ expect(location.label).to be_a(Symbol)
138
+ end
139
+
140
+ it 'returns :unknown for low familiarity' do
141
+ # familiarity_floor is 0.05, which is in the :unknown range (0.0...0.2)
142
+ expect(location.label).to eq(:unknown)
143
+ end
144
+
145
+ it 'returns :intimate for high familiarity' do
146
+ 10.times { location.visit }
147
+ expect(location.label).to eq(:intimate)
148
+ end
149
+ end
150
+
151
+ describe '#to_h' do
152
+ it 'returns a hash with expected keys' do
153
+ h = location.to_h
154
+ expect(h).to include(:id, :domain, :properties, :familiarity, :label, :visit_count, :neighbor_ids, :edge_count)
155
+ end
156
+
157
+ it 'reflects current state' do
158
+ location.visit
159
+ location.add_neighbor('other')
160
+ h = location.to_h
161
+ expect(h[:visit_count]).to eq(1)
162
+ expect(h[:edge_count]).to eq(1)
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/integration/map/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Integration::Map::Runners::CognitiveMap do
6
+ let(:client) { Legion::Extensions::Agentic::Integration::Map::Client.new }
7
+
8
+ def setup_two_locations
9
+ client.add_location(id: 'a', domain: :test)
10
+ client.add_location(id: 'b', domain: :test)
11
+ client.connect_locations(from: 'a', to: 'b', distance: 1.0)
12
+ end
13
+
14
+ describe '#add_location' do
15
+ it 'returns success: true for new location' do
16
+ result = client.add_location(id: 'concept:ruby')
17
+ expect(result[:success]).to be true
18
+ expect(result[:id]).to eq('concept:ruby')
19
+ end
20
+
21
+ it 'returns success: true for duplicate (idempotent)' do
22
+ client.add_location(id: 'concept:ruby')
23
+ result = client.add_location(id: 'concept:ruby')
24
+ expect(result[:success]).to be true
25
+ end
26
+
27
+ it 'passes domain through' do
28
+ result = client.add_location(id: 'loc1', domain: :science)
29
+ expect(result[:domain]).to eq(:science)
30
+ end
31
+ end
32
+
33
+ describe '#connect_locations' do
34
+ before { setup_two_locations }
35
+
36
+ it 'returns success: true' do
37
+ client.add_location(id: 'c')
38
+ result = client.connect_locations(from: 'a', to: 'c')
39
+ expect(result[:success]).to be true
40
+ end
41
+
42
+ it 'returns success: false for missing location' do
43
+ result = client.connect_locations(from: 'a', to: 'ghost')
44
+ expect(result[:success]).to be false
45
+ expect(result[:reason]).to eq(:missing_location)
46
+ end
47
+
48
+ it 'passes distance through' do
49
+ client.add_location(id: 'c')
50
+ result = client.connect_locations(from: 'a', to: 'c', distance: 2.5)
51
+ expect(result[:distance]).to eq(2.5)
52
+ end
53
+ end
54
+
55
+ describe '#visit_location' do
56
+ it 'returns success: true for existing location' do
57
+ client.add_location(id: 'loc1')
58
+ result = client.visit_location(id: 'loc1')
59
+ expect(result[:success]).to be true
60
+ expect(result[:visit_count]).to eq(1)
61
+ end
62
+
63
+ it 'returns success: false for missing location' do
64
+ result = client.visit_location(id: 'ghost')
65
+ expect(result[:success]).to be false
66
+ end
67
+
68
+ it 'increments visit count on repeated visits' do
69
+ client.add_location(id: 'loc1')
70
+ 3.times { client.visit_location(id: 'loc1') }
71
+ result = client.visit_location(id: 'loc1')
72
+ expect(result[:visit_count]).to eq(4)
73
+ end
74
+ end
75
+
76
+ describe '#find_path' do
77
+ before { setup_two_locations }
78
+
79
+ it 'finds path between connected locations' do
80
+ result = client.find_path(from: 'a', to: 'b')
81
+ expect(result[:success]).to be true
82
+ expect(result[:path]).to eq(%w[a b])
83
+ end
84
+
85
+ it 'returns success: false for disconnected locations' do
86
+ client.add_location(id: 'island')
87
+ result = client.find_path(from: 'a', to: 'island')
88
+ expect(result[:success]).to be false
89
+ end
90
+
91
+ it 'returns trivial path for same start and end' do
92
+ result = client.find_path(from: 'a', to: 'a')
93
+ expect(result[:success]).to be true
94
+ expect(result[:distance]).to eq(0.0)
95
+ end
96
+ end
97
+
98
+ describe '#explore_neighborhood' do
99
+ before { setup_two_locations }
100
+
101
+ it 'returns reachable locations' do
102
+ result = client.explore_neighborhood(id: 'a', max_distance: 2.0)
103
+ expect(result[:success]).to be true
104
+ expect(result[:count]).to be >= 1
105
+ end
106
+
107
+ it 'respects max_distance' do
108
+ result = client.explore_neighborhood(id: 'a', max_distance: 0.5)
109
+ expect(result[:count]).to eq(0)
110
+ end
111
+
112
+ it 'returns success: true even for isolated location' do
113
+ client.add_location(id: 'island')
114
+ result = client.explore_neighborhood(id: 'island', max_distance: 5.0)
115
+ expect(result[:success]).to be true
116
+ expect(result[:count]).to eq(0)
117
+ end
118
+ end
119
+
120
+ describe '#map_clusters' do
121
+ it 'returns cluster information' do
122
+ client.add_location(id: 'a')
123
+ client.add_location(id: 'b')
124
+ client.connect_locations(from: 'a', to: 'b')
125
+ result = client.map_clusters
126
+ expect(result[:success]).to be true
127
+ expect(result[:count]).to eq(1)
128
+ end
129
+
130
+ it 'returns zero clusters for empty map' do
131
+ result = client.map_clusters
132
+ expect(result[:count]).to eq(0)
133
+ end
134
+ end
135
+
136
+ describe '#familiar_locations' do
137
+ it 'returns locations sorted by familiarity' do
138
+ 3.times { |i| client.add_location(id: "loc_#{i}") }
139
+ 5.times { client.visit_location(id: 'loc_0') }
140
+ result = client.familiar_locations(limit: 3)
141
+ expect(result[:success]).to be true
142
+ expect(result[:locations].first[:id]).to eq('loc_0')
143
+ end
144
+
145
+ it 'respects limit' do
146
+ 5.times { |i| client.add_location(id: "loc_#{i}") }
147
+ result = client.familiar_locations(limit: 2)
148
+ expect(result[:count]).to be <= 2
149
+ end
150
+ end
151
+
152
+ describe '#switch_context' do
153
+ it 'switches to a new context' do
154
+ result = client.switch_context(context_id: :work)
155
+ expect(result[:success]).to be true
156
+ end
157
+
158
+ it 'new context has empty map' do
159
+ client.add_location(id: 'loc1')
160
+ client.switch_context(context_id: :empty_context)
161
+ stats = client.cognitive_map_stats
162
+ expect(stats[:location_count]).to eq(0)
163
+ end
164
+ end
165
+
166
+ describe '#update_cognitive_map' do
167
+ it 'runs decay cycle and returns counts' do
168
+ client.add_location(id: 'a')
169
+ client.visit_location(id: 'a')
170
+ result = client.update_cognitive_map
171
+ expect(result[:success]).to be true
172
+ expect(result).to include(:decayed, :pruned)
173
+ end
174
+ end
175
+
176
+ describe '#cognitive_map_stats' do
177
+ it 'returns stats with success: true' do
178
+ result = client.cognitive_map_stats
179
+ expect(result[:success]).to be true
180
+ expect(result).to include(:context, :location_count, :edge_count)
181
+ end
182
+
183
+ it 'reflects added locations' do
184
+ client.add_location(id: 'a')
185
+ client.add_location(id: 'b')
186
+ result = client.cognitive_map_stats
187
+ expect(result[:location_count]).to eq(2)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Mosaic::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'includes the runner module' do
7
+ expect(client).to respond_to(:create_tessera)
8
+ end
9
+
10
+ it 'includes all runner methods' do
11
+ %i[create_tessera create_mosaic place_tessera
12
+ list_tesserae list_mosaics mosaic_status].each do |m|
13
+ expect(client).to respond_to(m)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Mosaic::Helpers::Constants do
4
+ describe 'MATERIAL_TYPES' do
5
+ it 'has 10 material types' do
6
+ expect(described_class::MATERIAL_TYPES.size).to eq(10)
7
+ end
8
+
9
+ it 'includes core materials' do
10
+ %i[glass stone ceramic].each do |m|
11
+ expect(described_class::MATERIAL_TYPES).to include(m)
12
+ end
13
+ end
14
+ end
15
+
16
+ describe 'PATTERN_CATEGORIES' do
17
+ it 'includes standard categories' do
18
+ %i[geometric organic abstract narrative].each do |c|
19
+ expect(described_class::PATTERN_CATEGORIES).to include(c)
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '.label_for' do
25
+ it 'returns masterwork for high completeness' do
26
+ expect(described_class.label_for(described_class::COMPLETENESS_LABELS, 0.95)).to eq(:masterwork)
27
+ end
28
+
29
+ it 'returns nascent for low completeness' do
30
+ expect(described_class.label_for(described_class::COMPLETENESS_LABELS, 0.1)).to eq(:nascent)
31
+ end
32
+
33
+ it 'returns harmonious for high coherence' do
34
+ expect(described_class.label_for(described_class::COHERENCE_LABELS, 0.9)).to eq(:harmonious)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Mosaic::Helpers::MosaicEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ describe '#create_tessera' do
7
+ it 'creates and stores a tessera' do
8
+ t = engine.create_tessera(material: :glass, domain: :reasoning, content: 'test')
9
+ expect(t).to be_a(Legion::Extensions::Agentic::Integration::Mosaic::Helpers::Tessera)
10
+ end
11
+
12
+ it 'raises when limit reached' do
13
+ stub_const('Legion::Extensions::Agentic::Integration::Mosaic::Helpers::Constants::MAX_TESSERAE', 1)
14
+ engine.create_tessera(material: :glass, domain: :x, content: 'a')
15
+ expect { engine.create_tessera(material: :glass, domain: :x, content: 'b') }
16
+ .to raise_error(ArgumentError, /tessera limit/)
17
+ end
18
+ end
19
+
20
+ describe '#create_mosaic' do
21
+ it 'creates and stores a mosaic' do
22
+ m = engine.create_mosaic(name: 'test', pattern_category: :geometric)
23
+ expect(m).to be_a(Legion::Extensions::Agentic::Integration::Mosaic::Helpers::Mosaic)
24
+ end
25
+
26
+ it 'raises when limit reached' do
27
+ stub_const('Legion::Extensions::Agentic::Integration::Mosaic::Helpers::Constants::MAX_MOSAICS', 1)
28
+ engine.create_mosaic(name: 'a', pattern_category: :organic)
29
+ expect { engine.create_mosaic(name: 'b', pattern_category: :organic) }
30
+ .to raise_error(ArgumentError, /mosaic limit/)
31
+ end
32
+ end
33
+
34
+ describe '#place_tessera' do
35
+ it 'places tessera into mosaic' do
36
+ t = engine.create_tessera(material: :stone, domain: :memory, content: 'piece')
37
+ m = engine.create_mosaic(name: 'wall', pattern_category: :abstract)
38
+ engine.place_tessera(tessera_id: t.id, mosaic_id: m.id)
39
+ expect(t.placed?).to be true
40
+ expect(m.size).to eq(1)
41
+ end
42
+
43
+ it 'raises for already placed tessera' do
44
+ t = engine.create_tessera(material: :glass, domain: :x, content: 'a')
45
+ m = engine.create_mosaic(name: 'x', pattern_category: :geometric)
46
+ engine.place_tessera(tessera_id: t.id, mosaic_id: m.id)
47
+ m2 = engine.create_mosaic(name: 'y', pattern_category: :organic)
48
+ expect { engine.place_tessera(tessera_id: t.id, mosaic_id: m2.id) }
49
+ .to raise_error(ArgumentError, /already placed/)
50
+ end
51
+ end
52
+
53
+ describe '#erode_all_grout!' do
54
+ it 'erodes grout on all mosaics' do
55
+ m = engine.create_mosaic(name: 'wall', pattern_category: :geometric)
56
+ old = m.grout_strength
57
+ engine.erode_all_grout!
58
+ expect(m.grout_strength).to be < old
59
+ end
60
+ end
61
+
62
+ describe '#most_complete' do
63
+ it 'returns mosaics by completeness' do
64
+ engine.create_mosaic(name: 'empty', pattern_category: :organic, capacity: 10)
65
+ m2 = engine.create_mosaic(name: 'full', pattern_category: :organic, capacity: 2)
66
+ t1 = engine.create_tessera(material: :glass, domain: :x, content: 'a')
67
+ t2 = engine.create_tessera(material: :glass, domain: :x, content: 'b')
68
+ engine.place_tessera(tessera_id: t1.id, mosaic_id: m2.id)
69
+ engine.place_tessera(tessera_id: t2.id, mosaic_id: m2.id)
70
+ expect(engine.most_complete(limit: 1).first).to eq(m2)
71
+ end
72
+ end
73
+
74
+ describe '#loose_tesserae' do
75
+ it 'returns unplaced tesserae' do
76
+ t1 = engine.create_tessera(material: :glass, domain: :x, content: 'loose')
77
+ t2 = engine.create_tessera(material: :stone, domain: :x, content: 'placed')
78
+ m = engine.create_mosaic(name: 'wall', pattern_category: :geometric)
79
+ engine.place_tessera(tessera_id: t2.id, mosaic_id: m.id)
80
+ expect(engine.loose_tesserae).to eq([t1])
81
+ end
82
+ end
83
+
84
+ describe '#coherence' do
85
+ it 'returns 0.0 for empty mosaic' do
86
+ m = engine.create_mosaic(name: 'empty', pattern_category: :geometric)
87
+ expect(engine.coherence(mosaic_id: m.id)).to eq(0.0)
88
+ end
89
+
90
+ it 'returns positive for populated mosaic' do
91
+ m = engine.create_mosaic(name: 'wall', pattern_category: :geometric)
92
+ t = engine.create_tessera(material: :glass, domain: :x, content: 'a', fit_quality: 0.8)
93
+ engine.place_tessera(tessera_id: t.id, mosaic_id: m.id)
94
+ expect(engine.coherence(mosaic_id: m.id)).to be > 0.0
95
+ end
96
+ end
97
+
98
+ describe '#mosaic_report' do
99
+ it 'returns comprehensive report' do
100
+ engine.create_tessera(material: :glass, domain: :x, content: 'a')
101
+ report = engine.mosaic_report
102
+ %i[total_tesserae total_mosaics loose_count by_material
103
+ crumbling_count avg_completeness].each do |k|
104
+ expect(report).to have_key(k)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Mosaic::Helpers::Mosaic do
4
+ subject(:mosaic) do
5
+ described_class.new(name: 'Decision Map', pattern_category: :geometric)
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'sets name' do
10
+ expect(mosaic.name).to eq('Decision Map')
11
+ end
12
+
13
+ it 'sets pattern category' do
14
+ expect(mosaic.pattern_category).to eq(:geometric)
15
+ end
16
+
17
+ it 'defaults capacity to 50' do
18
+ expect(mosaic.capacity).to eq(50)
19
+ end
20
+
21
+ it 'defaults grout_strength to 0.8' do
22
+ expect(mosaic.grout_strength).to eq(0.8)
23
+ end
24
+
25
+ it 'starts empty' do
26
+ expect(mosaic.empty?).to be true
27
+ end
28
+
29
+ it 'rejects unknown pattern categories' do
30
+ expect { described_class.new(name: 'x', pattern_category: :random) }
31
+ .to raise_error(ArgumentError, /unknown pattern category/)
32
+ end
33
+ end
34
+
35
+ describe '#add_tessera' do
36
+ it 'adds tessera id' do
37
+ expect(mosaic.add_tessera('t1')).to be true
38
+ expect(mosaic.size).to eq(1)
39
+ end
40
+
41
+ it 'rejects duplicates' do
42
+ mosaic.add_tessera('t1')
43
+ expect(mosaic.add_tessera('t1')).to be false
44
+ end
45
+
46
+ it 'rejects when full' do
47
+ m = described_class.new(name: 'tiny', pattern_category: :organic, capacity: 1)
48
+ m.add_tessera('t1')
49
+ expect(m.add_tessera('t2')).to be false
50
+ end
51
+ end
52
+
53
+ describe '#remove_tessera' do
54
+ it 'removes existing tessera' do
55
+ mosaic.add_tessera('t1')
56
+ expect(mosaic.remove_tessera('t1')).to be true
57
+ expect(mosaic.size).to eq(0)
58
+ end
59
+
60
+ it 'returns false for missing tessera' do
61
+ expect(mosaic.remove_tessera('nope')).to be false
62
+ end
63
+ end
64
+
65
+ describe '#completeness' do
66
+ it 'returns 0.0 when empty' do
67
+ expect(mosaic.completeness).to eq(0.0)
68
+ end
69
+
70
+ it 'increases with tesserae' do
71
+ 5.times { |i| mosaic.add_tessera("t#{i}") }
72
+ expect(mosaic.completeness).to eq(0.1)
73
+ end
74
+ end
75
+
76
+ describe '#erode_grout!' do
77
+ it 'reduces grout strength' do
78
+ old = mosaic.grout_strength
79
+ mosaic.erode_grout!
80
+ expect(mosaic.grout_strength).to be < old
81
+ end
82
+ end
83
+
84
+ describe '#reinforce_grout!' do
85
+ it 'increases grout strength' do
86
+ mosaic.erode_grout!(rate: 0.3)
87
+ old = mosaic.grout_strength
88
+ mosaic.reinforce_grout!
89
+ expect(mosaic.grout_strength).to be > old
90
+ end
91
+ end
92
+
93
+ describe '#crumbling?' do
94
+ it 'returns false at default strength' do
95
+ expect(mosaic.crumbling?).to be false
96
+ end
97
+
98
+ it 'returns true when grout decays heavily' do
99
+ 20.times { mosaic.erode_grout!(rate: 0.1) }
100
+ expect(mosaic.crumbling?).to be true
101
+ end
102
+ end
103
+
104
+ describe '#gap_count' do
105
+ it 'equals capacity when empty' do
106
+ expect(mosaic.gap_count).to eq(50)
107
+ end
108
+ end
109
+
110
+ describe '#to_h' do
111
+ it 'returns all keys' do
112
+ h = mosaic.to_h
113
+ %i[id name pattern_category tessera_ids size capacity
114
+ completeness completeness_label grout_strength gap_count
115
+ full empty crumbling created_at].each do |k|
116
+ expect(h).to have_key(k)
117
+ end
118
+ end
119
+ end
120
+ end