orfeas_lyra 0.6.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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +222 -0
  3. data/LICENSE +21 -0
  4. data/README.md +1165 -0
  5. data/Rakefile +728 -0
  6. data/app/controllers/lyra/application_controller.rb +23 -0
  7. data/app/controllers/lyra/dashboard_controller.rb +624 -0
  8. data/app/controllers/lyra/flow_controller.rb +224 -0
  9. data/app/controllers/lyra/privacy_controller.rb +182 -0
  10. data/app/views/lyra/dashboard/audit_trail.html.erb +324 -0
  11. data/app/views/lyra/dashboard/discrepancies.html.erb +125 -0
  12. data/app/views/lyra/dashboard/event_graph_view.html.erb +525 -0
  13. data/app/views/lyra/dashboard/heatmap_view.html.erb +155 -0
  14. data/app/views/lyra/dashboard/index.html.erb +119 -0
  15. data/app/views/lyra/dashboard/model_overview.html.erb +115 -0
  16. data/app/views/lyra/dashboard/projections.html.erb +302 -0
  17. data/app/views/lyra/dashboard/schema.html.erb +283 -0
  18. data/app/views/lyra/dashboard/schema_history.html.erb +78 -0
  19. data/app/views/lyra/dashboard/schema_version.html.erb +340 -0
  20. data/app/views/lyra/dashboard/verification.html.erb +370 -0
  21. data/app/views/lyra/flow/crud_mapping.html.erb +125 -0
  22. data/app/views/lyra/flow/timeline.html.erb +260 -0
  23. data/app/views/lyra/privacy/pii_detection.html.erb +148 -0
  24. data/app/views/lyra/privacy/policy.html.erb +188 -0
  25. data/app/workflows/es_async_mode_workflow.rb +80 -0
  26. data/app/workflows/es_sync_mode_workflow.rb +64 -0
  27. data/app/workflows/hijack_mode_workflow.rb +54 -0
  28. data/app/workflows/lifecycle_workflow.rb +43 -0
  29. data/app/workflows/monitor_mode_workflow.rb +39 -0
  30. data/config/privacy_policies.rb +273 -0
  31. data/config/routes.rb +48 -0
  32. data/lib/lyra/aggregate.rb +131 -0
  33. data/lib/lyra/associations/event_aware.rb +225 -0
  34. data/lib/lyra/command.rb +81 -0
  35. data/lib/lyra/command_handler.rb +155 -0
  36. data/lib/lyra/configuration.rb +124 -0
  37. data/lib/lyra/consistency/read_your_writes.rb +91 -0
  38. data/lib/lyra/correlation.rb +144 -0
  39. data/lib/lyra/dual_view.rb +231 -0
  40. data/lib/lyra/engine.rb +67 -0
  41. data/lib/lyra/event.rb +71 -0
  42. data/lib/lyra/event_analyzer.rb +135 -0
  43. data/lib/lyra/event_flow.rb +449 -0
  44. data/lib/lyra/event_mapper.rb +106 -0
  45. data/lib/lyra/event_store_adapter.rb +72 -0
  46. data/lib/lyra/id_generator.rb +137 -0
  47. data/lib/lyra/interceptors/association_interceptor.rb +169 -0
  48. data/lib/lyra/interceptors/crud_interceptor.rb +543 -0
  49. data/lib/lyra/privacy/gdpr_compliance.rb +161 -0
  50. data/lib/lyra/privacy/pii_detector.rb +85 -0
  51. data/lib/lyra/privacy/pii_masker.rb +66 -0
  52. data/lib/lyra/privacy/policy_integration.rb +253 -0
  53. data/lib/lyra/projection.rb +94 -0
  54. data/lib/lyra/projections/async_projection_job.rb +63 -0
  55. data/lib/lyra/projections/cached_projection.rb +322 -0
  56. data/lib/lyra/projections/cached_relation.rb +757 -0
  57. data/lib/lyra/projections/event_store_reader.rb +127 -0
  58. data/lib/lyra/projections/model_projection.rb +143 -0
  59. data/lib/lyra/schema/diff.rb +331 -0
  60. data/lib/lyra/schema/event_class_registrar.rb +63 -0
  61. data/lib/lyra/schema/generator.rb +190 -0
  62. data/lib/lyra/schema/reporter.rb +188 -0
  63. data/lib/lyra/schema/store.rb +156 -0
  64. data/lib/lyra/schema/validator.rb +100 -0
  65. data/lib/lyra/strict_data_access.rb +363 -0
  66. data/lib/lyra/verification/crud_lifecycle_workflow.rb +456 -0
  67. data/lib/lyra/verification/workflow_generator.rb +540 -0
  68. data/lib/lyra/version.rb +3 -0
  69. data/lib/lyra/visualization/activity_heatmap.rb +215 -0
  70. data/lib/lyra/visualization/event_graph.rb +310 -0
  71. data/lib/lyra/visualization/timeline.rb +398 -0
  72. data/lib/lyra.rb +150 -0
  73. data/lib/tasks/dist.rake +391 -0
  74. data/lib/tasks/gems.rake +185 -0
  75. data/lib/tasks/lyra_schema.rake +231 -0
  76. data/lib/tasks/lyra_workflows.rake +452 -0
  77. data/lib/tasks/public_release.rake +351 -0
  78. data/lib/tasks/stats.rake +175 -0
  79. data/lib/tasks/testbed.rake +479 -0
  80. data/lib/tasks/version.rake +159 -0
  81. metadata +221 -0
@@ -0,0 +1,479 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :lyra do
4
+ namespace :testbed do
5
+ LYRA_TESTBED_PATH = File.expand_path("../../examples/aegean_epay_testbed", __dir__)
6
+
7
+ desc "Run testbed tests in all Lyra modes (part of comprehensive test suite)"
8
+ task :all_modes do
9
+ puts "\n" + "=" * 80
10
+ puts " TESTBED TESTS - ALL LYRA MODES"
11
+ puts " Path: #{LYRA_TESTBED_PATH}"
12
+ puts "=" * 80
13
+
14
+ Dir.chdir(LYRA_TESTBED_PATH) do
15
+ # Ensure bundle is up to date
16
+ system("bundle check || bundle install") || raise("Bundle install failed")
17
+
18
+ # Install required assets (pico CSS and fonts) before running tests
19
+ puts "\n[Setup] Installing required assets..."
20
+ system("bundle exec rails pico:update")
21
+ system("bundle exec rake fonts:install")
22
+
23
+ # Run the comprehensive all_modes test
24
+ success = system("bundle exec rake lyra:test:all_modes")
25
+ raise "Testbed tests failed in some modes" unless success
26
+ end
27
+
28
+ puts "\n" + "=" * 80
29
+ puts " TESTBED TESTS PASSED IN ALL MODES"
30
+ puts "=" * 80
31
+ end
32
+
33
+ desc "Run testbed tests in a specific mode (LYRA_MODE=disabled|monitor|hijack|event_sourcing)"
34
+ task :mode do
35
+ mode = ENV["LYRA_MODE"] || "disabled"
36
+ projection = ENV["LYRA_PROJECTION_MODE"]
37
+
38
+ puts "\n=== Running testbed tests in #{mode} mode ==="
39
+ puts " Projection mode: #{projection || 'default'}" if projection
40
+
41
+ Dir.chdir(LYRA_TESTBED_PATH) do
42
+ # Ensure required assets are installed
43
+ system("bundle exec rails pico:update > /dev/null 2>&1")
44
+ system("bundle exec rake fonts:install > /dev/null 2>&1")
45
+
46
+ env = { "LYRA_MODE" => mode, "RAILS_ENV" => "test" }
47
+ env["LYRA_PROJECTION_MODE"] = projection if projection
48
+
49
+ success = system(env, "bundle exec rails test")
50
+ raise "Testbed tests failed in #{mode} mode" unless success
51
+ end
52
+ end
53
+
54
+ desc "Run testbed integration tests only"
55
+ task :integration do
56
+ puts "\n=== Running testbed integration tests ==="
57
+
58
+ Dir.chdir(LYRA_TESTBED_PATH) do
59
+ # Ensure required assets are installed
60
+ system("bundle exec rails pico:update > /dev/null 2>&1")
61
+ system("bundle exec rake fonts:install > /dev/null 2>&1")
62
+
63
+ success = system("bundle exec rails test test/integration/")
64
+ raise "Testbed integration tests failed" unless success
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # Helper module for running tests and collecting results
71
+ module ComprehensiveTestRunner
72
+ LYRA_CORE_MODES = [
73
+ { lyra_mode: "disabled", projection_mode: nil, name: "Disabled" },
74
+ { lyra_mode: "monitor", projection_mode: nil, name: "Monitor" },
75
+ { lyra_mode: "hijack", projection_mode: nil, name: "Hijack" },
76
+ { lyra_mode: "event_sourcing", projection_mode: "sync", name: "ES Sync" },
77
+ { lyra_mode: "event_sourcing", projection_mode: "async", name: "ES Async" },
78
+ { lyra_mode: "event_sourcing", projection_mode: "disabled", name: "ES Disabled" }
79
+ ].freeze
80
+
81
+ TESTBED_MODES = [
82
+ { lyra_mode: "disabled", projection_mode: nil, name: "Lyra Disabled" },
83
+ { lyra_mode: "monitor", projection_mode: nil, name: "Monitor Mode" },
84
+ { lyra_mode: "hijack", projection_mode: nil, name: "Hijack Mode" },
85
+ { lyra_mode: "event_sourcing", projection_mode: "sync", name: "ES Sync" },
86
+ { lyra_mode: "event_sourcing", projection_mode: "async", name: "ES Async" },
87
+ { lyra_mode: "event_sourcing", projection_mode: "disabled", name: "ES Disabled" }
88
+ ].freeze
89
+
90
+ GEM_COMPONENTS = [
91
+ { path: "gems/pam_dsl", name: "PAM DSL" },
92
+ { path: "gems/petri_flow", name: "PetriFlow" }
93
+ ].freeze
94
+
95
+ class << self
96
+ def run_core_tests
97
+ require "open3"
98
+ results = []
99
+
100
+ LYRA_CORE_MODES.each_with_index do |config, index|
101
+ puts "\n[#{index + 1}/#{LYRA_CORE_MODES.size}] Core tests: #{config[:name]}"
102
+ puts "-" * 60
103
+
104
+ env = { "LYRA_MODE" => config[:lyra_mode] }
105
+ env["LYRA_PROJECTION_MODE"] = config[:projection_mode] if config[:projection_mode]
106
+
107
+ config_start = Time.now
108
+ stdout, stderr, status = Open3.capture3(env, "bundle exec rake test")
109
+ config_duration = Time.now - config_start
110
+
111
+ result = parse_test_output(stdout + stderr)
112
+ result[:name] = config[:name]
113
+ # Use parsed output for success (SimpleCov may return exit code 2 for coverage issues)
114
+ result[:passed] = result[:tests] > 0 && result[:failures] == 0 && result[:errors] == 0
115
+ result[:duration] = config_duration
116
+ results << result
117
+
118
+ print_result(result, config_duration)
119
+ end
120
+
121
+ results
122
+ end
123
+
124
+ def run_gem_tests
125
+ require "open3"
126
+ results = []
127
+ lyra_root = File.expand_path("../..", __dir__)
128
+
129
+ GEM_COMPONENTS.each_with_index do |component, index|
130
+ puts "\n[#{index + 1}/#{GEM_COMPONENTS.size}] #{component[:name]}"
131
+ puts "-" * 60
132
+
133
+ gem_path = File.join(lyra_root, component[:path])
134
+
135
+ # Use unbundled_env to use each gem's own Gemfile
136
+ Bundler.with_unbundled_env do
137
+ Dir.chdir(gem_path) do
138
+ system("bundle check > /dev/null 2>&1 || bundle install > /dev/null 2>&1")
139
+
140
+ config_start = Time.now
141
+ stdout, stderr, status = Open3.capture3("bundle exec rake test")
142
+ config_duration = Time.now - config_start
143
+
144
+ result = parse_test_output(stdout + stderr)
145
+ result[:name] = component[:name]
146
+ # Use parsed output for success (SimpleCov may return exit code 2 for coverage issues)
147
+ result[:passed] = result[:tests] > 0 && result[:failures] == 0 && result[:errors] == 0
148
+ result[:duration] = config_duration
149
+ results << result
150
+
151
+ print_result(result, config_duration)
152
+ end
153
+ end
154
+ end
155
+
156
+ results
157
+ end
158
+
159
+ def run_testbed_tests
160
+ require "open3"
161
+ testbed_path = File.expand_path("../../examples/aegean_epay_testbed", __dir__)
162
+ results = []
163
+
164
+ # Use unbundled_env to avoid inheriting Lyra's bundler environment
165
+ # The testbed has its own Gemfile with gems like prawn that Lyra doesn't have
166
+ Bundler.with_unbundled_env do
167
+ Dir.chdir(testbed_path) do
168
+ system("bundle check > /dev/null 2>&1 || bundle install > /dev/null 2>&1")
169
+
170
+ # Install required assets (pico CSS and fonts) before running tests
171
+ puts "\n[Setup] Installing required assets..."
172
+ system("bundle exec rails pico:update > /dev/null 2>&1")
173
+ system("bundle exec rake fonts:install > /dev/null 2>&1")
174
+
175
+ TESTBED_MODES.each_with_index do |config, index|
176
+ puts "\n[#{index + 1}/#{TESTBED_MODES.size}] Testbed: #{config[:name]}"
177
+ puts "-" * 60
178
+
179
+ # Reset database between modes to prevent state pollution
180
+ system("bundle exec rails db:test:prepare > /dev/null 2>&1")
181
+
182
+ env = { "LYRA_MODE" => config[:lyra_mode], "RAILS_ENV" => "test" }
183
+ env["LYRA_PROJECTION_MODE"] = config[:projection_mode] if config[:projection_mode]
184
+
185
+ config_start = Time.now
186
+ stdout, stderr, status = Open3.capture3(env, "bundle exec rails test")
187
+ config_duration = Time.now - config_start
188
+
189
+ result = parse_test_output(stdout + stderr)
190
+ result[:name] = config[:name]
191
+ # Use parsed output for success (SimpleCov may return exit code 2 for coverage issues)
192
+ result[:passed] = result[:tests] > 0 && result[:failures] == 0 && result[:errors] == 0
193
+ result[:duration] = config_duration
194
+ results << result
195
+
196
+ print_result(result, config_duration)
197
+
198
+ # Print failure details if any
199
+ unless result[:passed]
200
+ output = stdout + stderr
201
+ # Print last 100 lines to capture failure details
202
+ puts "\n--- FAILURE OUTPUT (last 100 lines) ---"
203
+ puts output.lines.last(100).join
204
+ puts "--- END FAILURE OUTPUT ---\n"
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ results
211
+ end
212
+
213
+ def run_no_pam_dsl_tests
214
+ require "open3"
215
+ results = []
216
+ lyra_root = File.expand_path("../..", __dir__)
217
+
218
+ LYRA_CORE_MODES.each_with_index do |config, index|
219
+ puts "\n[#{index + 1}/#{LYRA_CORE_MODES.size}] #{config[:name]} (no PAM DSL)"
220
+ puts "-" * 60
221
+
222
+ env = {
223
+ "LYRA_MODE" => config[:lyra_mode],
224
+ "LYRA_DISABLE_PAM_DSL" => "true"
225
+ }
226
+ env["LYRA_PROJECTION_MODE"] = config[:projection_mode] if config[:projection_mode]
227
+
228
+ config_start = Time.now
229
+ Dir.chdir(lyra_root) do
230
+ stdout, stderr, status = Open3.capture3(env, "bundle exec rake test")
231
+ config_duration = Time.now - config_start
232
+
233
+ result = parse_test_output(stdout + stderr)
234
+ result[:name] = config[:name]
235
+ # Use parsed output for success (SimpleCov may return exit code 2)
236
+ result[:passed] = result[:tests] > 0 && result[:failures] == 0 && result[:errors] == 0
237
+ result[:duration] = config_duration
238
+ results << result
239
+
240
+ print_result(result, config_duration)
241
+ end
242
+ end
243
+
244
+ results
245
+ end
246
+
247
+ def parse_test_output(output)
248
+ clean_output = output.gsub(/\e\[[0-9;]*m/, "")
249
+ if clean_output =~ /(\d+) tests?, (\d+) assertions?, (\d+) failures?, (\d+) errors?, (\d+) skips?/
250
+ { tests: $1.to_i, assertions: $2.to_i, failures: $3.to_i, errors: $4.to_i, skips: $5.to_i }
251
+ else
252
+ { tests: 0, assertions: 0, failures: 0, errors: 0, skips: 0 }
253
+ end
254
+ end
255
+
256
+ def print_result(result, duration)
257
+ status_icon = result[:passed] ? "\u2713" : "\u2717"
258
+ status_color = result[:passed] ? "\e[32m" : "\e[31m"
259
+ puts "#{status_color}#{status_icon}\e[0m #{result[:tests]} tests, #{result[:failures]} failures, " \
260
+ "#{result[:errors]} errors, #{result[:skips].to_i} skips (#{duration.round(1)}s)"
261
+ end
262
+
263
+ def generate_report(core_results, gem_results, testbed_results, total_duration, no_pam_dsl_results: [])
264
+ timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
265
+ # Save to testbed reports folder
266
+ reports_dir = File.expand_path("../../examples/aegean_epay_testbed/reports", __dir__)
267
+ FileUtils.mkdir_p(reports_dir)
268
+ report_path = File.join(reports_dir, "comprehensive_test_#{timestamp}.md")
269
+
270
+ report = generate_markdown_report(core_results, gem_results, testbed_results, total_duration, no_pam_dsl_results: no_pam_dsl_results)
271
+ File.write(report_path, report)
272
+
273
+ puts "\nReport saved to: #{report_path}"
274
+ report_path
275
+ end
276
+
277
+ def generate_markdown_report(core_results, gem_results, testbed_results, total_duration, no_pam_dsl_results: [])
278
+ core_passed = core_results.count { |r| r[:passed] }
279
+ gem_passed = gem_results.count { |r| r[:passed] }
280
+ testbed_passed = testbed_results.count { |r| r[:passed] }
281
+ no_pam_passed = no_pam_dsl_results.count { |r| r[:passed] }
282
+ all_passed = core_passed == core_results.size &&
283
+ gem_passed == gem_results.size &&
284
+ testbed_passed == testbed_results.size &&
285
+ (no_pam_dsl_results.empty? || no_pam_passed == no_pam_dsl_results.size)
286
+
287
+ no_pam_dsl_section = if no_pam_dsl_results.any?
288
+ <<~SECTION
289
+
290
+ ## Phase 4: Lyra Core Tests Without PAM DSL (All Modes)
291
+
292
+ | Mode | Tests | Assertions | Failures | Errors | Skips | Duration | Status |
293
+ |------|-------|------------|----------|--------|-------|----------|--------|
294
+ #{no_pam_dsl_results.map { |r| "| #{r[:name]} | #{r[:tests]} | #{r[:assertions]} | #{r[:failures]} | #{r[:errors]} | #{r[:skips]} | #{r[:duration].round(1)}s | #{r[:passed] ? "✅" : "❌"} |" }.join("\n")}
295
+ SECTION
296
+ else
297
+ ""
298
+ end
299
+
300
+ no_pam_summary_row = if no_pam_dsl_results.any?
301
+ "| Lyra (no PAM DSL) | #{no_pam_passed}/#{no_pam_dsl_results.size} | #{no_pam_dsl_results.sum { |r| r[:tests] }} | #{no_pam_dsl_results.sum { |r| r[:failures] }} | #{no_pam_dsl_results.sum { |r| r[:errors] }} | #{no_pam_dsl_results.sum { |r| r[:skips] }} |"
302
+ else
303
+ ""
304
+ end
305
+
306
+ <<~REPORT
307
+ # Comprehensive Test Report
308
+
309
+ **Generated:** #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}
310
+ **Status:** #{all_passed ? "✅ ALL PASSED" : "❌ FAILURES DETECTED"}
311
+ **Total Duration:** #{total_duration.round(1)}s
312
+
313
+ ## Summary
314
+
315
+ | Phase | Components Passed | Total Tests | Failures | Errors | Skips |
316
+ |-------|-------------------|-------------|----------|--------|-------|
317
+ | Lyra Core | #{core_passed}/#{core_results.size} | #{core_results.sum { |r| r[:tests] }} | #{core_results.sum { |r| r[:failures] }} | #{core_results.sum { |r| r[:errors] }} | #{core_results.sum { |r| r[:skips] }} |
318
+ | PAM DSL & PetriFlow | #{gem_passed}/#{gem_results.size} | #{gem_results.sum { |r| r[:tests] }} | #{gem_results.sum { |r| r[:failures] }} | #{gem_results.sum { |r| r[:errors] }} | #{gem_results.sum { |r| r[:skips] }} |
319
+ | Testbed | #{testbed_passed}/#{testbed_results.size} | #{testbed_results.sum { |r| r[:tests] }} | #{testbed_results.sum { |r| r[:failures] }} | #{testbed_results.sum { |r| r[:errors] }} | #{testbed_results.sum { |r| r[:skips] }} |
320
+ #{no_pam_summary_row}
321
+
322
+ ## Phase 1: Lyra Core Tests (All Modes)
323
+
324
+ | Mode | Tests | Assertions | Failures | Errors | Skips | Duration | Status |
325
+ |------|-------|------------|----------|--------|-------|----------|--------|
326
+ #{core_results.map { |r| "| #{r[:name]} | #{r[:tests]} | #{r[:assertions]} | #{r[:failures]} | #{r[:errors]} | #{r[:skips]} | #{r[:duration].round(1)}s | #{r[:passed] ? "✅" : "❌"} |" }.join("\n")}
327
+
328
+ ## Phase 2: PAM DSL & PetriFlow Tests
329
+
330
+ | Component | Tests | Assertions | Failures | Errors | Skips | Duration | Status |
331
+ |-----------|-------|------------|----------|--------|-------|----------|--------|
332
+ #{gem_results.map { |r| "| #{r[:name]} | #{r[:tests]} | #{r[:assertions]} | #{r[:failures]} | #{r[:errors]} | #{r[:skips]} | #{r[:duration].round(1)}s | #{r[:passed] ? "✅" : "❌"} |" }.join("\n")}
333
+
334
+ ## Phase 3: Testbed Tests (All Modes)
335
+
336
+ | Mode | Tests | Assertions | Failures | Errors | Skips | Duration | Status |
337
+ |------|-------|------------|----------|--------|-------|----------|--------|
338
+ #{testbed_results.map { |r| "| #{r[:name]} | #{r[:tests]} | #{r[:assertions]} | #{r[:failures]} | #{r[:errors]} | #{r[:skips]} | #{r[:duration].round(1)}s | #{r[:passed] ? "✅" : "❌"} |" }.join("\n")}
339
+ #{no_pam_dsl_section}
340
+ ## Environment
341
+
342
+ - **Ruby:** #{RUBY_VERSION}
343
+ - **Rails:** #{Rails::VERSION::STRING rescue "N/A"}
344
+ - **Platform:** #{RUBY_PLATFORM}
345
+ - **Git Branch:** #{`git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip rescue "unknown"}
346
+ - **Git Commit:** #{`git rev-parse --short HEAD 2>/dev/null`.strip rescue "unknown"}
347
+ REPORT
348
+ end
349
+ end
350
+ end
351
+
352
+ namespace :test do
353
+ desc "Run Lyra core tests without PAM DSL (all 6 modes)"
354
+ task :without_pam_dsl do
355
+ puts "\n" + "=" * 80
356
+ puts " LYRA CORE TESTS WITHOUT PAM DSL - ALL MODES"
357
+ puts " Environment: LYRA_DISABLE_PAM_DSL=true"
358
+ puts "=" * 80
359
+
360
+ require "open3"
361
+ results = []
362
+
363
+ ComprehensiveTestRunner::LYRA_CORE_MODES.each_with_index do |config, index|
364
+ puts "\n[#{index + 1}/#{ComprehensiveTestRunner::LYRA_CORE_MODES.size}] #{config[:name]} (no PAM DSL)"
365
+ puts "-" * 60
366
+
367
+ env = {
368
+ "LYRA_MODE" => config[:lyra_mode],
369
+ "LYRA_DISABLE_PAM_DSL" => "true"
370
+ }
371
+ env["LYRA_PROJECTION_MODE"] = config[:projection_mode] if config[:projection_mode]
372
+
373
+ config_start = Time.now
374
+ stdout, stderr, status = Open3.capture3(env, "bundle exec rake test")
375
+ config_duration = Time.now - config_start
376
+
377
+ result = ComprehensiveTestRunner.parse_test_output(stdout + stderr)
378
+ result[:name] = config[:name]
379
+ # Use parsed output for success (SimpleCov may return exit code 2)
380
+ result[:passed] = result[:tests] > 0 && result[:failures] == 0 && result[:errors] == 0
381
+ result[:duration] = config_duration
382
+ results << result
383
+
384
+ ComprehensiveTestRunner.print_result(result, config_duration)
385
+ end
386
+
387
+ passed = results.count { |r| r[:passed] }
388
+ total_duration = results.sum { |r| r[:duration] }
389
+
390
+ puts "\n" + "=" * 80
391
+ puts " LYRA WITHOUT PAM DSL SUMMARY: #{passed}/#{results.size} modes passed"
392
+ puts " Total Duration: #{total_duration.round(1)}s"
393
+ puts "=" * 80
394
+
395
+ exit 1 if results.any? { |r| !r[:passed] }
396
+ end
397
+
398
+ desc "Run Lyra core tests in all modes"
399
+ task :all_modes do
400
+ puts "\n" + "=" * 80
401
+ puts " LYRA CORE TESTS - ALL MODES"
402
+ puts "=" * 80
403
+
404
+ start_time = Time.now
405
+ results = ComprehensiveTestRunner.run_core_tests
406
+ total_duration = Time.now - start_time
407
+
408
+ passed = results.count { |r| r[:passed] }
409
+ puts "\n" + "=" * 80
410
+ puts " LYRA CORE TESTS SUMMARY: #{passed}/#{results.size} modes passed"
411
+ puts " Total Duration: #{total_duration.round(1)}s"
412
+ puts "=" * 80
413
+
414
+ exit 1 if results.any? { |r| !r[:passed] }
415
+ end
416
+
417
+ desc "Run comprehensive test suite (Lyra core + PAM DSL + PetriFlow + Testbed + No PAM DSL) with report"
418
+ task :comprehensive do
419
+ puts "\n" + "=" * 80
420
+ puts " COMPREHENSIVE TEST SUITE"
421
+ puts " Running: Lyra core (all modes) + PAM DSL + PetriFlow + Testbed (all modes) + No PAM DSL"
422
+ puts "=" * 80
423
+
424
+ start_time = Time.now
425
+ all_passed = true
426
+
427
+ # Phase 1: Lyra core tests
428
+ puts "\n" + "-" * 80
429
+ puts " PHASE 1: Lyra Core Tests (All Modes)"
430
+ puts "-" * 80
431
+ core_results = ComprehensiveTestRunner.run_core_tests
432
+ all_passed = false if core_results.any? { |r| !r[:passed] }
433
+
434
+ # Phase 2: PAM DSL & PetriFlow tests
435
+ puts "\n" + "-" * 80
436
+ puts " PHASE 2: PAM DSL & PetriFlow Tests"
437
+ puts "-" * 80
438
+ gem_results = ComprehensiveTestRunner.run_gem_tests
439
+ all_passed = false if gem_results.any? { |r| !r[:passed] }
440
+
441
+ # Phase 3: Testbed tests
442
+ puts "\n" + "-" * 80
443
+ puts " PHASE 3: Testbed Tests (All Modes)"
444
+ puts "-" * 80
445
+ testbed_results = ComprehensiveTestRunner.run_testbed_tests
446
+ all_passed = false if testbed_results.any? { |r| !r[:passed] }
447
+
448
+ # Phase 4: Lyra core tests without PAM DSL
449
+ puts "\n" + "-" * 80
450
+ puts " PHASE 4: Lyra Core Tests Without PAM DSL (All Modes)"
451
+ puts "-" * 80
452
+ no_pam_dsl_results = ComprehensiveTestRunner.run_no_pam_dsl_tests
453
+ all_passed = false if no_pam_dsl_results.any? { |r| !r[:passed] }
454
+
455
+ total_duration = Time.now - start_time
456
+
457
+ # Generate report
458
+ puts "\n" + "-" * 80
459
+ puts " GENERATING REPORT"
460
+ puts "-" * 80
461
+ report_path = ComprehensiveTestRunner.generate_report(
462
+ core_results, gem_results, testbed_results, total_duration,
463
+ no_pam_dsl_results: no_pam_dsl_results
464
+ )
465
+
466
+ # Final summary
467
+ puts "\n" + "=" * 80
468
+ if all_passed
469
+ puts " \e[32m✓ COMPREHENSIVE TEST SUITE PASSED\e[0m"
470
+ else
471
+ puts " \e[31m✗ COMPREHENSIVE TEST SUITE FAILED\e[0m"
472
+ end
473
+ puts " Total Duration: #{total_duration.round(1)}s"
474
+ puts " Report: #{report_path}"
475
+ puts "=" * 80
476
+
477
+ exit 1 unless all_passed
478
+ end
479
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :lyra do
4
+ VERSION_FILES = {
5
+ lyra: "lib/lyra/version.rb",
6
+ petri_flow: "gems/petri_flow/lib/petri_flow/version.rb",
7
+ pam_dsl: "gems/pam_dsl/lib/pam_dsl/version.rb"
8
+ }.freeze
9
+
10
+ desc "Show current versions of all components"
11
+ task :version do
12
+ puts "\n" + "=" * 50
13
+ puts "Lyra/ORFEAS Component Versions"
14
+ puts "=" * 50
15
+
16
+ versions = {}
17
+ VERSION_FILES.each do |name, file|
18
+ versions[name] = extract_version(file)
19
+ puts "%-15s %s" % [name, versions[name]]
20
+ end
21
+
22
+ puts "-" * 50
23
+ lyra_v = Gem::Version.new(versions[:lyra])
24
+ all_valid = [:petri_flow, :pam_dsl].all? do |gem|
25
+ Gem::Version.new(versions[gem]) <= lyra_v
26
+ end
27
+
28
+ if all_valid
29
+ puts "Status: OK (Lyra >= all gem versions)"
30
+ else
31
+ puts "Status: WARNING (Lyra should be >= all gem versions)"
32
+ end
33
+ puts "=" * 50 + "\n"
34
+ end
35
+
36
+ desc "Set version for components. Scope: all (default), lyra, petri_flow, pam_dsl, gems"
37
+ task :set_version, [:version, :scope] do |_t, args|
38
+ unless args[:version]
39
+ puts "Usage:"
40
+ puts " rake lyra:set_version[1.0.0] # All components"
41
+ puts " rake lyra:set_version[1.0.0,lyra] # Lyra only"
42
+ puts " rake lyra:set_version[1.0.0,petri_flow] # PetriFlow + Lyra"
43
+ puts " rake lyra:set_version[1.0.0,pam_dsl] # PAM DSL + Lyra"
44
+ puts " rake lyra:set_version[1.0.0,gems] # Both gems + Lyra"
45
+ exit 1
46
+ end
47
+
48
+ new_version = args[:version]
49
+ scope = (args[:scope] || "all").to_sym
50
+
51
+ unless new_version.match?(/^\d+\.\d+\.\d+$/)
52
+ puts "Error: Version must be in format X.Y.Z (e.g., 1.0.0)"
53
+ exit 1
54
+ end
55
+
56
+ # Determine which components to update
57
+ components = case scope
58
+ when :all
59
+ [:lyra, :petri_flow, :pam_dsl]
60
+ when :lyra
61
+ [:lyra]
62
+ when :petri_flow
63
+ [:lyra, :petri_flow]
64
+ when :pam_dsl
65
+ [:lyra, :pam_dsl]
66
+ when :gems
67
+ [:lyra, :petri_flow, :pam_dsl]
68
+ else
69
+ puts "Unknown scope: #{scope}"
70
+ puts "Valid scopes: all, lyra, petri_flow, pam_dsl, gems"
71
+ exit 1
72
+ end
73
+
74
+ puts "\n" + "=" * 50
75
+ puts "Setting Version to #{new_version}"
76
+ puts "Scope: #{scope}"
77
+ puts "=" * 50
78
+
79
+ # Validate: Lyra must be >= gem versions
80
+ new_v = Gem::Version.new(new_version)
81
+ VERSION_FILES.each do |name, file|
82
+ next if components.include?(name)
83
+ current_v = Gem::Version.new(extract_version(file))
84
+ if current_v > new_v && name != :lyra
85
+ puts "Error: Cannot set Lyra to #{new_version} - #{name} is at #{current_v}"
86
+ puts "Lyra version must be >= all gem versions"
87
+ exit 1
88
+ end
89
+ end
90
+
91
+ components.each do |name|
92
+ file = VERSION_FILES[name]
93
+ old_version = extract_version(file)
94
+ update_version_file(file, new_version)
95
+ puts "#{name}: #{old_version} -> #{new_version}"
96
+ end
97
+
98
+ # Show final state
99
+ puts "-" * 50
100
+ puts "Final versions:"
101
+ VERSION_FILES.each do |name, file|
102
+ puts " %-15s %s" % [name, extract_version(file)]
103
+ end
104
+ puts "=" * 50 + "\n"
105
+ end
106
+
107
+ desc "Bump version (major/minor/patch). Scope: all (default), lyra, petri_flow, pam_dsl, gems"
108
+ task :bump, [:type, :scope] do |_t, args|
109
+ type = args[:type] || "patch"
110
+ scope = args[:scope] || "all"
111
+
112
+ # Calculate new version based on Lyra's current version
113
+ current = extract_version(VERSION_FILES[:lyra])
114
+ new_version = calculate_new_version(current, type)
115
+
116
+ # Invoke set_version with the calculated version
117
+ Rake::Task["lyra:set_version"].invoke(new_version, scope)
118
+ end
119
+
120
+ private
121
+
122
+ def extract_version(file)
123
+ return "0.0.0" unless File.exist?(file)
124
+
125
+ content = File.read(file)
126
+ match = content.match(/VERSION\s*=\s*["']([^"']+)["']/)
127
+ match ? match[1] : "0.0.0"
128
+ end
129
+
130
+ def calculate_new_version(current, type)
131
+ parts = current.split(".").map(&:to_i)
132
+ parts = [0, 0, 0] if parts.length < 3
133
+
134
+ case type.to_s.downcase
135
+ when "major"
136
+ "#{parts[0] + 1}.0.0"
137
+ when "minor"
138
+ "#{parts[0]}.#{parts[1] + 1}.0"
139
+ when "patch"
140
+ "#{parts[0]}.#{parts[1]}.#{parts[2] + 1}"
141
+ else
142
+ if type.match?(/^\d+\.\d+\.\d+$/)
143
+ type
144
+ else
145
+ puts "Unknown version type: #{type}"
146
+ puts "Use: major, minor, patch, or a specific version (e.g., 1.0.0)"
147
+ exit 1
148
+ end
149
+ end
150
+ end
151
+
152
+ def update_version_file(file, new_version)
153
+ return unless File.exist?(file)
154
+
155
+ content = File.read(file)
156
+ updated = content.gsub(/VERSION\s*=\s*["'][^"']+["']/, "VERSION = \"#{new_version}\"")
157
+ File.write(file, updated)
158
+ end
159
+ end