orfeas_lyra 0.6.0 → 0.9.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +851 -0
  3. data/README.md +196 -987
  4. data/app/controllers/lyra/application_controller.rb +48 -0
  5. data/app/controllers/lyra/dashboard_controller.rb +0 -1
  6. data/app/views/lyra/dashboard/projections.html.erb +0 -4
  7. data/app/views/lyra/dashboard/verification.html.erb +8 -2
  8. data/app/workflows/es_async_mode_workflow.rb +24 -60
  9. data/app/workflows/es_sync_mode_workflow.rb +25 -50
  10. data/app/workflows/hijack_mode_workflow.rb +18 -33
  11. data/app/workflows/lifecycle_workflow.rb +1 -1
  12. data/app/workflows/monitor_mode_workflow.rb +12 -22
  13. data/config/privacy_policies.rb +2 -1
  14. data/config/routes.rb +4 -2
  15. data/lib/lyra/access_log.rb +121 -0
  16. data/lib/lyra/advisory_lock.rb +45 -0
  17. data/lib/lyra/aggregate.rb +25 -13
  18. data/lib/lyra/associations/event_aware.rb +3 -4
  19. data/lib/lyra/associations/habtm_links.rb +255 -0
  20. data/lib/lyra/bypass_events.rb +208 -0
  21. data/lib/lyra/command.rb +3 -0
  22. data/lib/lyra/command_handler.rb +164 -42
  23. data/lib/lyra/configuration.rb +217 -8
  24. data/lib/lyra/consistency/read_your_writes.rb +74 -23
  25. data/lib/lyra/domain_events.rb +157 -0
  26. data/lib/lyra/dual_view.rb +27 -6
  27. data/lib/lyra/dual_view_sampler.rb +33 -0
  28. data/lib/lyra/encrypted_attributes.rb +101 -0
  29. data/lib/lyra/engine.rb +67 -4
  30. data/lib/lyra/erasure.rb +316 -0
  31. data/lib/lyra/event.rb +120 -2
  32. data/lib/lyra/event_flow.rb +16 -6
  33. data/lib/lyra/event_serializer.rb +45 -0
  34. data/lib/lyra/event_store_adapter.rb +58 -62
  35. data/lib/lyra/genesis.rb +189 -0
  36. data/lib/lyra/id_generator.rb +42 -10
  37. data/lib/lyra/interceptors/association_interceptor.rb +123 -6
  38. data/lib/lyra/interceptors/crud_interceptor.rb +400 -74
  39. data/lib/lyra/interceptors/lazy_reads.rb +46 -0
  40. data/lib/lyra/mode_sync.rb +125 -0
  41. data/lib/lyra/mode_transition.rb +386 -0
  42. data/lib/lyra/model_selection.rb +160 -0
  43. data/lib/lyra/optimistic_locking.rb +88 -0
  44. data/lib/lyra/optional_dependency.rb +24 -0
  45. data/lib/lyra/own_connection.rb +54 -0
  46. data/lib/lyra/privacy/adapters/pam.rb +151 -0
  47. data/lib/lyra/privacy/gdpr_compliance.rb +51 -1
  48. data/lib/lyra/privacy/interface.rb +262 -0
  49. data/lib/lyra/privacy/pii_detector.rb +8 -10
  50. data/lib/lyra/privacy/pii_masker.rb +30 -13
  51. data/lib/lyra/privacy/policy_integration.rb +39 -148
  52. data/lib/lyra/projection.rb +30 -7
  53. data/lib/lyra/projections/association_conditions.rb +81 -0
  54. data/lib/lyra/projections/async_checkpoint.rb +57 -0
  55. data/lib/lyra/projections/async_projection_job.rb +68 -15
  56. data/lib/lyra/projections/cached_joins.rb +192 -0
  57. data/lib/lyra/projections/cached_projection.rb +279 -163
  58. data/lib/lyra/projections/cached_relation.rb +666 -85
  59. data/lib/lyra/projections/deferred_relation.rb +120 -0
  60. data/lib/lyra/projections/event_store_reader.rb +27 -4
  61. data/lib/lyra/projections/lazy_projection.rb +259 -0
  62. data/lib/lyra/projections/model_projection.rb +32 -34
  63. data/lib/lyra/projections/rebuild.rb +170 -0
  64. data/lib/lyra/projections/reference_links.rb +309 -0
  65. data/lib/lyra/purpose_bound_reads.rb +197 -0
  66. data/lib/lyra/repair.rb +114 -0
  67. data/lib/lyra/retention.rb +180 -0
  68. data/lib/lyra/schema/event_class_registrar.rb +2 -1
  69. data/lib/lyra/schema/validator.rb +14 -5
  70. data/lib/lyra/sti_stream_migration.rb +70 -0
  71. data/lib/lyra/strict_data_access.rb +225 -175
  72. data/lib/lyra/temporal.rb +128 -0
  73. data/lib/lyra/verification/bypass_workflow.rb +170 -0
  74. data/lib/lyra/verification/crud_lifecycle_workflow.rb +32 -6
  75. data/lib/lyra/verification/workflow_generator.rb +73 -179
  76. data/lib/lyra/version.rb +1 -1
  77. data/lib/lyra.rb +114 -17
  78. data/lib/tasks/lyra_mode.rake +89 -0
  79. data/lib/tasks/lyra_projections.rake +78 -0
  80. data/lib/tasks/lyra_schema.rake +4 -0
  81. data/lib/tasks/lyra_workflows.rake +28 -27
  82. metadata +39 -11
  83. data/lib/lyra/event_mapper.rb +0 -106
  84. data/lib/tasks/dist.rake +0 -391
  85. data/lib/tasks/gems.rake +0 -185
  86. data/lib/tasks/public_release.rake +0 -351
  87. data/lib/tasks/stats.rake +0 -175
  88. data/lib/tasks/testbed.rake +0 -479
data/README.md CHANGED
@@ -4,984 +4,104 @@
4
4
 
5
5
  *Part of the ORFEAS (Object-Relational to Event-Sourcing Architecture) Framework*
6
6
 
7
- ## Author
8
-
9
- **Michail Pantelelis** (mpantel@aegean.gr)
10
- PhD Candidate, University of the Aegean
11
- Department of Information and Communication Systems Engineering
12
-
13
- ---
14
-
15
- ## Table of Contents
16
-
17
- 1. [Overview](#overview)
18
- 2. [The Problem](#the-problem)
19
- 3. [The Solution: ORFEAS Framework](#the-solution-orfeas-framework)
20
- 4. [Key Features](#key-features)
21
- 5. [Architecture](#architecture)
22
- 6. [Components](#components)
23
- 7. [Installation](#installation)
24
- 8. [Quick Start](#quick-start)
25
- 9. [Usage Guide](#usage-guide)
26
- 10. [Example Application](#example-application)
27
- 11. [Benefits](#benefits)
28
- 12. [Documentation](#documentation)
29
- 13. [Research Foundation](#research-foundation)
30
- 14. [Development Methodology](#development-methodology)
31
- 15. [Contributing](#contributing)
32
- 16. [Citation](#citation)
33
- 17. [Support](#support)
34
-
35
- ---
36
-
37
- ## Overview
38
-
39
- Lyra is a Rails engine that enables **gradual, non-intrusive transformation** of traditional CRUD (Create, Read, Update, Delete) operations into Event Sourcing architectures, with built-in privacy compliance capabilities. It is the practical implementation of the **ORFEAS (Object-Relational to Event-Sourcing Architecture)** framework, developed as part of a PhD thesis addressing the fundamental challenge of bridging 40+ years of ORM dominance with modern event-driven, GDPR-compliant application architectures.
40
-
41
- ### What Makes Lyra Unique?
42
-
43
- - **Dual-Mode Operation**: Monitor existing applications non-intrusively, then gradually transform to full event sourcing
44
- - **Formal Mathematical Foundation**: Built on Petri nets (P/T nets for verification, CPNs for advanced modeling) and matrix analysis for rigorous CRUD-to-event mapping
45
- - **Privacy-First Design**: Integrated Privacy Attribute Matrix (PAM) for GDPR compliance
46
- - **Zero-Downtime Migration**: Maintain CRUD as a safety net while transitioning to event sourcing
47
- - **Research-Backed**: Grounded in peer-reviewed research on event sourcing patterns and privacy preservation
48
-
49
- ---
50
-
51
- ## The Problem
52
-
53
- Modern enterprise systems face a critical architectural challenge:
54
-
55
- ### The Legacy Dilemma
56
- - **40+ Years of ORM Investment**: Decades of business logic encoded in Object-Relational Mapping systems
57
- - **CRUD Limitations**: State-based systems lose behavioral history and temporal context
58
- - **Privacy Requirements**: GDPR mandates comprehensive data lineage and processing transparency
59
- - **Migration Risk**: Complete rewrites from CRUD to Event Sourcing are expensive and risky
60
-
61
- ### The Event Sourcing Promise
62
- - **Complete Audit Trails**: Every state change recorded as an immutable event
63
- - **Temporal Queries**: Query system state at any point in time
64
- - **Behavioral Analysis**: Understand *how* and *why* state changes occurred
65
- - **Microservices Alignment**: Natural fit for distributed, event-driven architectures
66
-
67
- ### The Gap
68
- **There is no formal, proven method for gradually migrating CRUD systems to event sourcing while maintaining operational safety and privacy compliance.**
69
-
70
- ---
71
-
72
- ## The Solution: ORFEAS Framework
73
-
74
- The **ORFEAS (Object-Relational to Event-Sourcing Architecture)** framework provides:
75
-
76
- 1. **Formal Mathematical Models**: Petri nets map CRUD operations to event sequences with formal verification (P/T nets for structural proofs, CPNs optional for data modeling)
77
- 2. **Privacy Compliance**: Privacy Attribute Matrix (PAM) ensures GDPR compliance throughout the transformation
78
- 3. **Gradual Migration Path**: Two operational modes support safe, incremental transition
79
- 4. **Dual-View Analysis**: Compare CRUD and event-sourced views to verify correctness
80
- 5. **Practical Tooling**: Lyra implements ORFEAS as a production-ready Rails engine
81
-
82
- ### Theoretical Foundation
83
-
84
- ORFEAS is built on three pillars:
85
-
86
- 1. **Petri Nets**: Model CRUD-to-event transformations (P/T nets for verification proofs; CPNs with tokens, guards, and arc expressions for advanced data modeling)
87
- 2. **Matrix Analysis**: Complementary linear algebra approach for causation and lineage tracking
88
- 3. **Privacy Attribute Matrix (PAM)**: DSL for declaring field-level privacy policies and transformations
89
-
90
- [Read the complete theoretical foundation →](docs/ORFEAS_FRAMEWORK_OVERVIEW.md)
91
-
92
- ---
93
-
94
- ## Key Features
95
-
96
- ### Operational Features
97
- - ✅ **Non-intrusive Monitoring** - Works with existing Rails applications without code changes
98
- - ✅ **Dual Operational Modes** - Monitor or hijack CRUD operations
99
- - ✅ **Automatic Event Mapping** - CRUD operations automatically mapped to domain events
100
- - ✅ **State Reconstruction** - Rebuild state from event streams
101
- - ✅ **Dual-View Dashboard** - Compare CRUD state vs event-sourced state
102
-
103
- ### Technical Features
104
- - ✅ **Rails Event Store Integration** - Built on proven event sourcing infrastructure
105
- - ✅ **Pluggable Event Backends** - Support for custom event storage (Kafka, EventStoreDB)
106
- - ✅ **Aggregate Support** - Custom domain aggregates for complex business logic
107
- - ✅ **Command/Query Separation** - CQRS-ready architecture
108
- - ✅ **Event Versioning** - Support for event schema evolution
109
-
110
- ### Privacy & Compliance
111
- - ✅ **Privacy Attribute Matrix (PAM)** - Built-in PAM DSL for defining privacy policies
112
- - ✅ **GDPR Compliance** - Purpose-based access control, consent management, retention policies
113
- - ✅ **PII Detection & Transformation** - Automatic PII field identification and transformation
114
- - ✅ **Audit Trails** - Complete lineage tracking for regulatory compliance
115
-
116
- ---
117
-
118
- ## Architecture
119
-
120
- ### High-Level Architecture
121
-
122
- ```
123
- ┌─────────────────────────────────────────────────────────────┐
124
- │ Rails Application │
125
- ├─────────────────────────────────────────────────────────────┤
126
- │ │
127
- │ ActiveRecord Models (with monitor_with_lyra) │
128
- │ │ │
129
- │ ▼ │
130
- │ ┌──────────────────┐ │
131
- │ │ CRUD Interceptor │ │
132
- │ └────────┬─────────┘ │
133
- │ │ │
134
- │ ┌────┴────┐ │
135
- │ │ │ │
136
- │ MONITOR HIJACK │
137
- │ MODE MODE │
138
- │ │ │ │
139
- │ ▼ ▼ │
140
- │ ┌─────┐ ┌─────────┐ │
141
- │ │ Log │ │ Command │ │
142
- │ │Event│ │ Handler │ │
143
- │ └──┬──┘ └────┬────┘ │
144
- │ │ │ │
145
- │ └─────┬─────┘ │
146
- │ ▼ │
147
- │ ┌─────────────────┐ │
148
- │ │ Event Store │ │
149
- │ │ (Rails Event │ │
150
- │ │ Store) │ │
151
- │ └─────────────────┘ │
152
- │ │ │
153
- │ ▼ │
154
- │ ┌─────────────────┐ │
155
- │ │ Aggregates & │ │
156
- │ │ Projections │ │
157
- │ └─────────────────┘ │
158
- │ │
159
- └─────────────────────────────────────────────────────────────┘
160
- ```
161
-
162
- ### Operational Modes
163
-
164
- #### 1. Monitor Mode (Non-intrusive)
165
- - Observes CRUD operations and logs them as domain events
166
- - **No changes** to application behavior
167
- - Perfect for **analysis and planning** migration
168
- - Zero risk to production systems
169
-
170
- #### 2. Hijack Mode (Transformative)
171
- - Intercepts CRUD operations and routes through event sourcing
172
- - Replaces traditional relational backend
173
- - Maintains CRUD interface for **backward compatibility**
174
- - Full event sourcing benefits
175
-
176
- [See detailed architecture documentation →](docs/ARCHITECTURE.md)
177
-
178
- ---
179
-
180
- ## Components
181
-
182
- ### Lyra Engine (Core)
183
- The main Rails engine providing:
184
- - CRUD interception and monitoring
185
- - Event mapping and publishing
186
- - Dual-view analysis
187
- - Command/Query handlers
188
- - State reconstruction
189
-
190
- ### PetriFlow Gem
191
- Complete Petri net and Colored Petri Net library:
192
- - Core Petri net components (places, transitions, arcs)
193
- - Colored extensions (guards, arc expressions, token types)
194
- - Matrix analysis (CRUD-event mapping, causation, lineage)
195
- - Visualization (GraphViz, Mermaid, ASCII)
196
- - Formal verification (reachability, boundedness, liveness)
197
- - Export functionality (PNML, CPN Tools, JSON, YAML)
198
-
199
- [Read the PetriFlow documentation →](gems/petri_flow/README.md)
200
-
201
- ### PAM DSL Gem
202
- Privacy Attribute Matrix Domain-Specific Language:
203
- - Field-level PII classification with sensitivity levels
204
- - Purpose-based access control aligned with GDPR
205
- - Retention policies with field-level granularity
206
- - Consent management with expiration tracking
207
- - Data transformation for different contexts (display, logging, API)
208
-
209
- [Read the PAM DSL documentation →](gems/pam_dsl/README.md)
210
-
211
- ### Monorepo Structure
212
-
213
- This repository is organized as a monorepo:
214
- - **`/` (root)** - Lyra Rails engine
215
- - **`gems/petri_flow/`** - PetriFlow Petri net library
216
- - **`gems/pam_dsl/`** - PAM DSL privacy policy language
217
- - **`docs/`** - Theoretical documentation
218
- - **`docs-site/`** - Jekyll documentation site
219
- - **`examples/`** - Example applications
220
-
221
- [See complete monorepo structure →](docs/MONOREPO.md)
222
-
223
- ---
224
-
225
- ## Installation
226
-
227
- ### Prerequisites
228
-
229
- - Ruby 3.4.5+ (tested up to Ruby 4.0) and Rails 8.0+
230
- - PostgreSQL 14+ (recommended for Rails Event Store)
231
- - Bundler
232
-
233
- ### Add to Gemfile
234
-
235
- ```ruby
236
- gem 'lyra', path: 'path/to/lyra' # or from git/rubygems when published
237
- gem 'pam_dsl', '~> 0.1.0' # Privacy Attribute Matrix DSL
238
- gem 'petri_flow', '~> 0.1.0' # Petri net library
239
- ```
240
-
241
- ### Install Dependencies
242
-
243
- ```bash
244
- bundle install
245
- rails generate rails_event_store_active_record:migration
246
- rails db:migrate
247
- ```
248
-
249
- ---
250
-
251
- ## Quick Start
252
-
253
- ### 1. Configure Lyra
254
-
255
- Create `config/initializers/lyra.rb`:
256
-
257
- ```ruby
258
- Lyra.configure do |config|
259
- # Start in monitor mode (non-intrusive)
260
- config.mode = :monitor
261
-
262
- # Configure event store
263
- config.event_backend = :rails_event_store
264
- config.event_store = RailsEventStore::Client.new
265
-
266
- # Optional: User tracking for audit trails (see User Tracking section below)
267
- # config.metadata_proc = ->(record, operation) { { user_id: Current.user&.id } }
268
- end
269
- ```
270
-
271
- ### 2. Monitor a Model
272
-
273
- Add monitoring to your ActiveRecord models:
274
-
275
- ```ruby
276
- class Order < ApplicationRecord
277
- # Enable Lyra monitoring
278
- monitor_with_lyra
279
-
280
- # Your existing code continues to work normally
281
- validates :total, presence: true
282
- belongs_to :customer
283
- end
284
- ```
285
-
286
- ### 3. Observe Events
287
-
288
- All CRUD operations are now logged as events:
289
-
290
- ```ruby
291
- order = Order.create!(total: 100, customer: customer)
292
- # => Publishes OrderCreated event
293
-
294
- order.update!(total: 150)
295
- # => Publishes OrderUpdated event
296
-
297
- order.destroy!
298
- # => Publishes OrderDestroyed event
299
- ```
300
-
301
- ### 4. Analyze State
302
-
303
- Compare CRUD vs event-sourced views:
304
-
305
- ```ruby
306
- comparison = Lyra::DualView.new(Order, order.id).compare
307
-
308
- puts comparison[:differences]
309
- # => { no_differences: true }
310
-
311
- # View complete audit trail
312
- audit = Lyra::DualView.new(Order, order.id).audit_trail
313
- # => [{ timestamp: ..., operation: :created, changes: {...} }, ...]
314
- ```
315
-
316
- [Continue to full usage guide →](#usage-guide)
7
+ Lyra is a Rails engine that records the writes an ActiveRecord application
8
+ makes as events (RailsEventStore), and can then move the application, one
9
+ checked step at a time, to treating those events as the source of truth.
317
10
 
318
11
  ---
319
12
 
320
- ## Usage Guide
321
-
322
- ### Basic Model Monitoring
323
-
324
- Add monitoring to any ActiveRecord model:
325
-
326
- ```ruby
327
- class Order < ApplicationRecord
328
- monitor_with_lyra
329
-
330
- validates :total, presence: true
331
- belongs_to :customer
332
- end
333
- ```
334
-
335
- ### Privacy Policies with PAM DSL
336
-
337
- Define privacy policies for your models:
338
-
339
- ```ruby
340
- # config/privacy_policies.rb
341
- PamDsl.define_policy :order_system do
342
- # Define PII fields
343
- field :email, type: :email, sensitivity: :internal do
344
- allow_for :order_processing, :communication
345
- transform :display { |v| "#{v[0]}***@#{v.split('@').last}" }
346
- end
347
-
348
- field :credit_card, type: :credit_card, sensitivity: :restricted do
349
- allow_for :payment_processing
350
- transform :display { |v| "****-****-****-#{v[-4..]}" }
351
- end
352
-
353
- # Define processing purposes
354
- purpose :order_processing do
355
- basis :contract
356
- requires :email
357
- end
358
-
359
- purpose :marketing do
360
- basis :consent
361
- requires :email
362
- end
363
-
364
- # Configure retention
365
- retention do
366
- for_model 'Order' do
367
- keep_for 7.years
368
- on_expiry :anonymize
369
- end
370
- end
371
- end
372
-
373
- # Apply to model
374
- class Order < ApplicationRecord
375
- monitor_with_lyra privacy_policy: :order_system
376
- end
377
- ```
378
-
379
- [See complete PAM DSL guide →](gems/pam_dsl/docs/PAM_DSL_INTEGRATION.md)
380
-
381
- ### Schema Validation (Strict Mode)
382
-
383
- Lyra can enforce schema consistency to prevent silent breaking changes in production:
384
-
385
- ```ruby
386
- # config/initializers/lyra.rb
387
- Lyra.configure do |config|
388
- config.mode = :monitor
389
-
390
- # Enable strict schema validation (recommended for production)
391
- config.strict_schema = Rails.env.production?
392
-
393
- # Custom schema storage path (optional, defaults to db/lyra_schemas/)
394
- config.schema_path = Rails.root.join('db/lyra_schemas')
395
-
396
- config.monitor_model User
397
- config.monitor_model Order
398
- end
399
- ```
400
-
401
- #### Schema Management Rake Tasks
402
-
403
- ```bash
404
- # Generate initial schema from monitored models
405
- rake lyra:schema:create
406
-
407
- # Check for schema changes without updating
408
- rake lyra:schema:verify
409
-
410
- # Create new schema version (after migrations)
411
- rake lyra:schema:update
412
-
413
- # Display model → event mappings
414
- rake lyra:schema:report
415
-
416
- # Show schema version history
417
- rake lyra:schema:history
418
-
419
- # Compare two schema versions
420
- rake lyra:schema:diff[1,2]
421
- ```
422
-
423
- #### Schema Validation Workflow
424
-
425
- 1. **Development**: Run `rake lyra:schema:create` to generate initial schema
426
- 2. **After migrations**: Run `rake lyra:schema:verify` to detect changes
427
- 3. **If changes detected**: Run `rake lyra:schema:update` to create new version
428
- 4. **Production**: With `strict_schema = true`, app fails to start if schema changes
429
-
430
- #### Schema Change Severity Levels
431
-
432
- | Severity | Changes | Impact |
433
- |----------|---------|--------|
434
- | **BREAKING** | model_removed, column_removed, column_type_changed | Requires new schema version |
435
- | **WARNING** | column_nullable_changed, pii_field_added | Review recommended |
436
- | **INFO** | model_added, column_added | Documentation update |
437
-
438
- ### User Tracking (metadata_proc)
439
-
440
- Lyra can capture custom metadata with each event, such as the current user, for audit trails and GDPR compliance.
441
-
442
- #### Configuration
443
-
444
- ```ruby
445
- # config/initializers/lyra.rb
446
- Lyra.configure do |config|
447
- config.mode = :monitor
448
- config.event_backend = :rails_event_store
449
-
450
- # Custom metadata proc - called for every event
451
- # Signature: ->(record, operation) { Hash }
452
- config.metadata_proc = lambda do |record, operation|
453
- {
454
- user_id: Current.user&.id,
455
- user_email: Current.user&.email,
456
- ip_address: Current.request&.remote_ip,
457
- source: 'my_app'
458
- }
459
- end
460
- end
461
- ```
462
-
463
- #### Using with Rails CurrentAttributes
464
-
465
- Rails 5.2+ provides `CurrentAttributes` for request-scoped state:
466
-
467
- ```ruby
468
- # app/models/current.rb
469
- class Current < ActiveSupport::CurrentAttributes
470
- attribute :user, :request_id, :request
471
- end
472
-
473
- # app/controllers/application_controller.rb
474
- class ApplicationController < ActionController::Base
475
- before_action :set_current_attributes
476
-
477
- private
478
-
479
- def set_current_attributes
480
- Current.user = current_user
481
- Current.request_id = request.request_id
482
- Current.request = request
483
- end
484
- end
485
- ```
486
-
487
- #### Using with Devise/Warden
488
-
489
- For Devise-based authentication:
490
-
491
- ```ruby
492
- config.metadata_proc = lambda do |record, operation|
493
- user = if defined?(Warden) && Thread.current[:request_env]
494
- Warden::Proxy.new(Thread.current[:request_env], Warden::Manager.new(nil)).user
495
- end
496
-
497
- { user_id: user&.id, user_email: user&.email }
498
- end
499
- ```
500
-
501
- #### Using with Rails Engines (Solidus, Spree, etc.)
502
-
503
- Many Rails Engines provide their own `Current` class:
504
-
505
- ```ruby
506
- config.metadata_proc = lambda do |record, operation|
507
- user_id = nil
508
-
509
- # Try standard Current
510
- user_id ||= Current.user&.id if defined?(Current)
511
-
512
- # Try Spree/Solidus Current
513
- user_id ||= Spree::Current.user&.id if defined?(Spree::Current)
514
-
515
- { user_id: user_id }
516
- end
517
- ```
518
-
519
- #### Metadata in Events
520
-
521
- The custom metadata is merged with Lyra's built-in metadata:
522
-
523
- ```ruby
524
- event = Lyra.event_store.read.last
525
- event.metadata
526
- # => {
527
- # user_id: 123,
528
- # user_email: "admin@example.com",
529
- # ip_address: "127.0.0.1",
530
- # source: "my_app",
531
- # request_id: "abc-123", # Built-in from Current
532
- # correlation_id: "corr-456", # Built-in from Correlation context
533
- # causation_id: "cause-789" # Built-in from Causation context
534
- # }
535
- ```
536
-
537
- ### Advanced Configuration
538
-
539
- Use custom event names and aggregates:
540
-
541
- ```ruby
542
- class Order < ApplicationRecord
543
- monitor_with_lyra(
544
- event_prefix: 'Order',
545
- aggregate_class: OrderAggregate
546
- )
547
- end
548
-
549
- class OrderAggregate < Lyra::Aggregate
550
- def total
551
- get_state(:total)
552
- end
553
-
554
- private
555
-
556
- def apply_order_created(event)
557
- @id = event.model_id
558
- set_state(:total, event.attributes['total'])
559
- set_state(:status, 'pending')
560
- end
561
-
562
- def apply_order_updated(event)
563
- event.changes.each { |k, (old, new)| set_state(k.to_sym, new) }
564
- end
565
- end
566
- ```
567
-
568
- ### Event Capture Behavior
569
-
570
- Lyra uses ActiveRecord `after_commit` callbacks to capture events. This means **all** operations that go through ActiveRecord are captured, regardless of source:
571
-
572
- | Source | Captured |
573
- |--------|----------|
574
- | Web requests | ✓ |
575
- | Rails console (`rails c`) | ✓ |
576
- | Background jobs (Sidekiq, etc.) | ✓ |
577
- | Rake tasks | ✓ |
578
- | Rails runner scripts | ✓ |
579
- | Seeds (`db:seed`) | ✓ |
580
-
581
- **Operations NOT captured** (bypass ActiveRecord callbacks):
582
-
583
- | Operation | Captured | Alternative |
584
- |-----------|----------|-------------|
585
- | `update_columns` / `update_column` | ✗ | Use `update!` |
586
- | `update_all` / `delete_all` | ✗ | Iterate with `find_each` |
587
- | `insert_all` / `upsert_all` | ✗ | Use `create!` |
588
- | Raw SQL (`connection.execute`) | ✗ | Use ActiveRecord methods |
589
- | `touch` with no callbacks | ✗ | Use `update!` |
590
-
591
- ### Strict Data Access Mode
592
-
593
- To prevent inconsistencies between CRUD state and event store, enable strict mode to raise errors on callback-bypassing operations:
594
-
595
- ```ruby
596
- # config/initializers/lyra.rb
597
- Lyra.configure do |config|
598
- config.strict_data_access = true # Raises on update_columns, etc.
599
- # Or only in specific environments:
600
- config.strict_data_access = Rails.env.development? || Rails.env.test?
601
- end
602
- ```
603
-
604
- When enabled, monitored models will raise `Lyra::StrictDataAccessViolation` if you attempt:
605
-
606
- | Operation | Alternative | Scope |
607
- |-----------|-------------|-------|
608
- | `update_columns` | `update!` | Instance |
609
- | `update_column` | `update!` | Instance |
610
- | `delete` | `destroy` | Instance |
611
- | `update_all` | `find_each { \|r\| r.update!(...) }` | Relation |
612
- | `delete_all` | `find_each(&:destroy)` | Relation |
613
- | `insert_all` | `records.each { \|attrs\| create!(attrs) }` | Class |
614
- | `upsert_all` | `find_or_create_by!(...).update!(...)` | Class |
615
-
616
- ```ruby
617
- user.update_columns(name: "New")
618
- # => raises Lyra::StrictDataAccessViolation:
619
- # "update_columns bypasses callbacks and won't be captured by Lyra.
620
- # Use update! instead. Disable strict_data_access mode if this is intentional."
621
-
622
- Registration.where(status: "pending").update_all(status: "expired")
623
- # => raises Lyra::StrictDataAccessViolation:
624
- # "update_all bypasses callbacks. Use find_each { |r| r.update!(...) } instead."
625
- ```
626
-
627
- #### Bypassing Strict Mode
628
-
629
- For legitimate bulk operations in migrations, seeds, or admin tasks, use `Lyra.without_strict_access`:
630
-
631
- ```ruby
632
- # Temporarily bypass strict mode for bulk operations
633
- Lyra.without_strict_access do
634
- User.where(active: false).delete_all # No error raised
635
- end
636
- ```
637
-
638
- **Note:** Rails association operations (like `dependent: :nullify`) are automatically allowed since they're internal framework operations.
639
-
640
- ### Dual View Analysis
641
-
642
- Compare CRUD state with event-sourced state:
643
-
644
- ```ruby
645
- # Single record comparison
646
- comparison = Lyra::DualView.new(Order, order_id).compare
647
-
648
- puts comparison[:crud_view]
649
- # => { exists: true, attributes: {...} }
650
-
651
- puts comparison[:event_sourced_view]
652
- # => { exists: true, state: {...}, events_count: 5 }
653
-
654
- puts comparison[:differences]
655
- # => { no_differences: true } or differences hash
656
-
657
- # Audit trail
658
- audit = Lyra::DualView.new(Order, order_id).audit_trail
659
- # => Array of all operations with timestamps and changes
660
- ```
661
-
662
- ### Batch Analysis
663
-
664
- Find discrepancies across all records:
665
-
666
- ```ruby
667
- discrepancies = Lyra::DualView.find_discrepancies(Order)
668
- # => Returns all orders where CRUD state != Event-sourced state
669
-
670
- analysis = Lyra::StateAnalyzer.analyze(Order, order_id)
671
- puts analysis[:recommendations]
672
- # => Actionable recommendations based on state comparison
673
- ```
674
-
675
- ### Switching to Hijack Mode
676
-
677
- After analyzing in monitor mode, switch to full event sourcing:
678
-
679
- ```ruby
680
- # In config/initializers/lyra.rb
681
- Lyra.configure do |config|
682
- config.enable_hijack!
683
- end
684
- ```
685
-
686
- Now CRUD operations are intercepted and routed through event sourcing:
687
-
688
- ```ruby
689
- order = Order.create!(total: 100, customer: customer)
690
- # => CreateCommand processed
691
- # => OrderCreated event published
692
- # => Aggregate updated
693
- # => Database record created with event-sourced ID
694
- ```
695
-
696
- ### Dashboard
697
-
698
- Lyra provides a web dashboard for monitoring and analyzing event-sourced data.
699
-
700
- #### Mounting the Dashboard
701
-
702
- Add to your `config/routes.rb`:
703
-
704
- ```ruby
705
- Rails.application.routes.draw do
706
- mount Lyra::Engine, at: "/lyra"
707
- # ... rest of your routes
708
- end
709
- ```
710
-
711
- #### Available Routes
712
-
713
- | Route | Description |
714
- |-------|-------------|
715
- | `/lyra/dashboard` | Main dashboard with monitored models overview |
716
- | `/lyra/dashboard/model/:class` | Model-specific overview (e.g., `/lyra/dashboard/model/Order`) |
717
- | `/lyra/dashboard/compare/:class/:id` | Dual View - Compare CRUD state vs Event-sourced state |
718
- | `/lyra/dashboard/discrepancies/:class` | List records with state discrepancies |
719
-
720
- **Event Flow Routes:**
721
-
722
- | Route | Description |
723
- |-------|-------------|
724
- | `/lyra/flow/timeline` | Global event timeline |
725
- | `/lyra/flow/event_chain/:class/:id` | Event chain for a specific record |
726
- | `/lyra/flow/crud_mapping` | CRUD operation to event type mapping |
727
- | `/lyra/flow/correlation/:correlation_id` | Events by correlation ID |
728
- | `/lyra/flow/user_actions/:user_id` | Events by user |
729
-
730
- **Visualization Routes:**
731
-
732
- | Route | Description |
733
- |-------|-------------|
734
- | `/lyra/visualizations/event_graph` | Interactive event graph (HTML + Mermaid) |
735
- | `/lyra/visualizations/event_graph.json` | Event graph data with filters |
736
- | `/lyra/visualizations/entity_graph/:class/:id.json` | Entity lifecycle graph |
737
- | `/lyra/visualizations/heatmap` | Activity heatmap view |
738
- | `/lyra/visualizations/heatmap.json` | Heatmap data for time period |
739
- | `/lyra/visualizations/event_list.json` | List of entities with event counts |
740
-
741
- **Event Graph Features:**
742
-
743
- The event graph provides an interactive visualization of entity lifecycles:
744
-
745
- - **Entity Picker**: Filter by model class, select specific entities to view
746
- - **Changed Fields**: Each event node displays which fields were modified (📝 prefix)
747
- - **Link Types**:
748
- - Solid lines → Same entity lifecycle (chronological order)
749
- - Dashed lines → Correlated events (same transaction/request)
750
- - **Node Details**: Includes operation type, timestamp, and changed field names
751
-
752
- Example node display:
753
- ```
754
- ┌─────────────────────┐
755
- │ UPDATED │
756
- │ 14:30:45 │
757
- │ 📝 status, amount │
758
- └─────────────────────┘
759
- ```
760
-
761
- Timestamp fields (`*_at`) are automatically excluded from the changed fields display.
762
-
763
- **Schema Management Routes:**
764
-
765
- | Route | Description |
766
- |-------|-------------|
767
- | `/lyra/dashboard/schema` | Current schema with pending changes |
768
- | `/lyra/dashboard/schema/history` | Schema version history |
769
- | `/lyra/dashboard/schema/:version` | View specific schema version |
770
-
771
- **Formal Verification Routes (requires PetriFlow):**
772
-
773
- | Route | Description |
774
- |-------|-------------|
775
- | `/lyra/verification` | Verification dashboard |
776
- | `/lyra/verification.json` | Verification results (JSON) |
777
-
778
- **Privacy & GDPR Routes:**
779
-
780
- | Route | Description |
781
- |-------|-------------|
782
- | `/lyra/privacy/pii_detection` | Automatic PII field detection |
783
- | `/lyra/privacy/gdpr_report/:type/:id` | GDPR Article 15 compliant report |
784
- | `/lyra/privacy/subject/:type/:id` | View all data for a subject |
785
-
786
- #### Securing Dashboard Access
787
-
788
- **IMPORTANT**: The Lyra dashboard exposes sensitive data including PII fields, event history, and audit trails. Always restrict access in production.
789
-
790
- ##### Option 1: Authentication Constraint (Recommended)
791
-
792
- ```ruby
793
- # config/routes.rb
794
- Rails.application.routes.draw do
795
- # Restrict to authenticated admin users
796
- authenticate :user, ->(u) { u.admin? } do
797
- mount Lyra::Engine, at: "/lyra"
798
- end
799
- end
800
- ```
801
-
802
- ##### Option 2: Basic HTTP Authentication
803
-
804
- ```ruby
805
- # config/routes.rb
806
- Rails.application.routes.draw do
807
- mount Lyra::Engine, at: "/lyra", constraints: ->(req) {
808
- Rack::Auth::Basic::Request.new(req.env).provided? &&
809
- Rack::Auth::Basic::Request.new(req.env).credentials ==
810
- [ENV['LYRA_USER'], ENV['LYRA_PASSWORD']]
811
- }
812
- end
813
- ```
814
-
815
- ##### Option 3: IP Whitelist
816
-
817
- ```ruby
818
- # config/routes.rb
819
- Rails.application.routes.draw do
820
- constraints ->(req) { ['127.0.0.1', '::1'].include?(req.remote_ip) } do
821
- mount Lyra::Engine, at: "/lyra"
822
- end
823
- end
824
- ```
825
-
826
- ##### Option 4: Custom Middleware
827
-
828
- ```ruby
829
- # lib/lyra_auth_middleware.rb
830
- class LyraAuthMiddleware
831
- def initialize(app)
832
- @app = app
833
- end
834
-
835
- def call(env)
836
- if env['PATH_INFO'].start_with?('/lyra')
837
- # Your authentication logic here
838
- return [403, {}, ['Forbidden']] unless authorized?(env)
839
- end
840
- @app.call(env)
841
- end
842
-
843
- private
844
-
845
- def authorized?(env)
846
- # Implement your authorization logic
847
- env['warden']&.user&.admin?
848
- end
849
- end
850
-
851
- # config/application.rb
852
- config.middleware.use LyraAuthMiddleware
853
- ```
854
-
855
- ##### Option 5: Disable in Production
856
-
857
- ```ruby
858
- # config/routes.rb
859
- Rails.application.routes.draw do
860
- unless Rails.env.production?
861
- mount Lyra::Engine, at: "/lyra"
862
- end
863
- end
864
- ```
865
-
866
- #### Environment-Based Configuration
867
-
868
- ```ruby
869
- # config/routes.rb
870
- Rails.application.routes.draw do
871
- case Rails.env
872
- when 'development'
873
- # Open access in development
874
- mount Lyra::Engine, at: "/lyra"
875
- when 'staging'
876
- # Basic auth in staging
877
- mount Lyra::Engine, at: "/lyra", constraints: LyraBasicAuth
878
- when 'production'
879
- # Full authentication in production
880
- authenticate :user, ->(u) { u.admin? } do
881
- mount Lyra::Engine, at: "/lyra"
882
- end
883
- end
884
- end
885
- ```
886
-
887
- ### Custom Event Mappers
888
-
889
- Create custom event mapping logic:
890
-
891
- ```ruby
892
- class OrderEventMapper < Lyra::EventMapper
893
- def event_data
894
- super.merge(
895
- business_context: {
896
- total: data[:attributes]['total'],
897
- items_count: data[:attributes]['items_count']
898
- }
899
- )
900
- end
901
- end
902
-
903
- Lyra::EventMapper.register_mapper(Order, OrderEventMapper)
904
- ```
905
-
906
- ### State Reconstruction
907
-
908
- Rebuild current state from events:
909
-
910
- ```ruby
911
- # Using projection
912
- state = Lyra::StateProjection.rebuild_state(Order, order_id)
913
- # => { total: 150, status: "confirmed", ... }
914
-
915
- # Using aggregate
916
- aggregate = OrderAggregate.load(order_id)
917
- aggregate.total # => 150
918
- aggregate.status # => "confirmed"
919
- ```
920
-
921
- ### Pluggable Backends
922
-
923
- Implement custom event storage:
13
+ ## Modes
14
+
15
+ The mode is application-wide. Per model you choose only whether it is
16
+ monitored (`monitor_with_lyra` in the model, or `config.models=` in the
17
+ initializer). Event sourcing takes one of four projection modes, which gives
18
+ seven configurations:
19
+
20
+ | Configuration | `mode` | `projection_mode` | Authoritative store |
21
+ |---|---|---|---|
22
+ | Disabled | `:disabled` | – | tables |
23
+ | Monitor | `:monitor` (default) | – | tables |
24
+ | Hijack | `:hijack` | – | events |
25
+ | ES-Sync | `:event_sourcing` | `:sync` (default) | events |
26
+ | ES-Async | `:event_sourcing` | `:async` | events |
27
+ | ES-NoProj | `:event_sourcing` | `:disabled` | events |
28
+ | ES-Lazy | `:event_sourcing` | `:lazy` | events |
29
+
30
+ - **Monitor** appends the event in `after_create`/`after_update`/`after_destroy`,
31
+ inside the write's transaction (not `after_commit`).
32
+ - **Hijack and event sourcing** build the event in hooks prepended to
33
+ ActiveRecord's persistence, after the model's `before_*` callbacks.
34
+ - **Callback-bypassing writes** (`update_columns`, `update_column`, `touch`,
35
+ `delete`, `update_all`, `delete_all`, `insert_all`, `insert_all!`, `upsert_all`,
36
+ and the updates of `dependent: :nullify`) on a monitored model publish bypass
37
+ events; with `config.strict_data_access = true` they raise instead (except
38
+ `touch` and the `dependent: :nullify` updates).
39
+ - Each record has its own stream, named `"Model$id"` (for example `"Order$42"`).
40
+ - **ES-NoProj** writes no rows; it answers a query exactly from the events or
41
+ raises `Lyra::Projections::UnsupportedQuery`.
42
+ - **Switching modes after boot is gated**: `Lyra::ModeTransition.to!` (and
43
+ `config.enable_hijack!`, `enable_monitor!`, `enable_event_sourcing!`) checks
44
+ that rows and events agree before a switch that changes the authoritative
45
+ store, and the engine refuses to boot into such a switch without a
46
+ certificate from `rake lyra:mode:check`. See
47
+ [Switching Modes](docs/MIGRATION_GUIDE.md#switching-modes).
48
+
49
+ ## Requirements and installation
50
+
51
+ - Ruby >= 4.0, Rails >= 8.0, `rails_event_store` ~> 3.0 (from `lyra.gemspec`).
52
+ - PostgreSQL. Lyra depends on the `pg` gem; the advisory locks behind Genesis
53
+ and ES-Lazy, and id reservation in Hijack mode, need PostgreSQL.
54
+
55
+ The gem names differ from the files to require:
924
56
 
925
57
  ```ruby
926
- class MyEventStore < Lyra::CustomEventStoreAdapter
927
- def publish(event, stream_name:)
928
- # Custom implementation (e.g., Kafka, EventStoreDB)
929
- end
930
-
931
- def read_stream(stream_name)
932
- # Custom implementation
933
- end
934
- end
935
-
936
- Lyra.configure do |config|
937
- config.event_backend = :custom
938
- config.event_store = MyEventStore.new
939
- end
940
- ```
58
+ # Gemfile
59
+ gem "orfeas_lyra", path: "path/to/lyra", require: "lyra"
60
+ gem "orfeas_pam_dsl", path: "path/to/lyra/gems/pam_dsl", require: "pam_dsl" # optional, privacy features
61
+ gem "orfeas_petri_flow", path: "path/to/lyra/gems/petri_flow", require: "petri_flow" # optional, verification
62
+ ```
63
+
64
+ Create the event store tables with the RailsEventStore 3 generator
65
+ (`bin/rails generate ruby_event_store:active_record:migration`), then add an
66
+ initializer. The [Migration Guide](docs/MIGRATION_GUIDE.md) walks through the
67
+ whole path from Disabled to event sourcing.
68
+
69
+ ## Features
70
+
71
+ Beyond the modes above (see the [API Reference](docs/API_REFERENCE.md) for each):
72
+
73
+ - **Genesis**: rows that predate Lyra get an `Imported` event on first use, or
74
+ ahead of time with `rake lyra:genesis`.
75
+ - **Failure policies**: Hijack and event sourcing are fail-closed (the write
76
+ rolls back); Monitor logs and continues, and `rake lyra:repair` brings the
77
+ event log back in line with the tables.
78
+ - **DualView**: compare a record's table row with its event-sourced state;
79
+ optionally sampled after commit (`config.dual_view_sample_rate`, off by default).
80
+ - **Point-in-time state**: `Lyra.state_at(Model, id, time)` and `Model.as_of(time)`.
81
+ - **Privacy** (all opt-in; most need the pam_dsl gem): purpose-bound reads,
82
+ an access log, privacy stamps in event metadata, erasure of one record's
83
+ personal data from its row and its events (`rake lyra:erase`), and retention
84
+ rules (`rake lyra:retention:apply`).
85
+ - **Formal verification** (needs petri_flow): Petri net models of each mode's
86
+ write path, checked with PetriFlow (`rake lyra:workflows:verify`), and a
87
+ check of the CRUD-to-event mapping (`Lyra.verify_mapping!`).
88
+ - **Dashboard**: `mount Lyra::Engine, at: "/lyra"`. Outside development and
89
+ test it answers 403 until `config.dashboard_authorization` (a proc run on the
90
+ controller, e.g. `->(controller) { controller.current_user&.admin? }`) says
91
+ who may use it.
941
92
 
942
93
  ---
943
94
 
944
- ## Example Applications
945
-
946
- ### Aegean E-Pay Testbed (Comprehensive)
947
-
948
- A full-featured university payment system testbed is included in `examples/aegean_epay_testbed/`. This real-world example demonstrates:
949
-
950
- - **CRUD Operations** - Registrations, payments, refunds with automatic event generation
951
- - **Dual-View Comparison** - Compare CRUD state vs event-sourced state
952
- - **Privacy Policy Enforcement** - PAM DSL integration for GDPR compliance
953
- - **Audit Trail Generation** - Complete history from immutable events
954
- - **State Machine Workflows** - Refund request lifecycle with events
955
- - **Performance Benchmarking** - Compare overhead with/without Lyra
956
-
957
- #### Running the Testbed
958
-
959
- ```bash
960
- cd examples/aegean_epay_testbed
961
- bundle install
962
- rails db:create db:migrate db:seed
963
-
964
- # Run integration tests
965
- ruby test_lyra_integration.rb
966
-
967
- # Or with performance benchmarks
968
- ruby test_lyra_integration.rb --performance
969
- ```
970
-
971
- [Explore the Aegean E-Pay testbed →](examples/aegean_epay_testbed/README.md)
972
-
973
- ### Blog App (Getting Started)
974
-
975
- A simpler example for learning Lyra basics is in `examples/blog_app/`:
976
-
977
- ```bash
978
- cd examples/blog_app
979
- bundle install
980
- rails db:create db:migrate db:seed
981
- rails console
982
- ```
95
+ ## Example application
983
96
 
984
- [Explore the blog example →](examples/blog_app/README.md)
97
+ `examples/blog_app/` is a small blog (users, posts, comments) in Monitor, on
98
+ Rails 8.1, RailsEventStore 3 and the repository's PostgreSQL
99
+ (`docker compose up -d db`, then `bin/setup` and `bin/rails test` in the app);
100
+ see its [README](examples/blog_app/README.md). The files
101
+ [`examples/usage_examples.rb`](examples/usage_examples.rb),
102
+ [`examples/privacy_examples.rb`](examples/privacy_examples.rb) and
103
+ [`examples/privacy_policy_usage.rb`](examples/privacy_policy_usage.rb) show
104
+ individual calls.
985
105
 
986
106
  ---
987
107
 
@@ -991,21 +111,21 @@ rails console
991
111
  - **Orthogonal Analysis**: Compare static vs. dynamic views of system state
992
112
  - **Behavioral Analysis**: Understand system evolution over time
993
113
  - **Migration Patterns**: Study CRUD-to-ES transformation strategies
994
- - **Formal Verification**: Prove correctness using Petri net theory
114
+ - **Formal Verification**: Check the CRUD-to-event mapping with Petri net models (petri_flow)
995
115
  - **Privacy Compliance**: Research privacy-preserving event sourcing patterns
996
116
 
997
117
  ### For Development
998
- - **Zero Downtime Migration**: Gradually transition to event sourcing
999
- - **Audit Trail**: Complete history of all state changes
1000
- - **Temporal Queries**: Query state at any point in time
118
+ - **Gradual Migration**: Move to event sourcing one checked mode switch at a time
119
+ - **Audit Trail**: History of every write to a monitored model that goes through ActiveRecord
120
+ - **Temporal Queries**: Query state at a point in time
1001
121
  - **Debugging**: Replay events to understand issues
1002
- - **CQRS Support**: Natural separation of commands and queries
122
+ - **CQRS Support**: Commands and aggregates alongside the projected tables
1003
123
 
1004
124
  ### For Operations
1005
- - **Non-intrusive**: Deploy without code changes
1006
- - **Rollback Safety**: Keep CRUD as safety net during transition
1007
- - **Real-time Monitoring**: Compare CRUD vs event-sourced state
1008
- - **Validation**: Verify event sourcing correctness before full migration
125
+ - **Few code changes**: A model is monitored by one line or by naming it in the initializer; controllers and queries stay as they are (see [Adopting Lyra](docs/MIGRATION_GUIDE.md#adopting-lyra-in-an-existing-application) for what this costs)
126
+ - **Rollback**: Every mode can be switched back to the previous one
127
+ - **Monitoring**: Compare table and event-sourced state (DualView)
128
+ - **Validation**: A switch that makes the events authoritative is checked first
1009
129
  - **Performance Analysis**: Measure overhead before committing
1010
130
 
1011
131
  ---
@@ -1013,12 +133,19 @@ rails console
1013
133
  ## Documentation
1014
134
 
1015
135
  ### Core Documentation
1016
- - **[Getting Started Guide](docs/GETTING_STARTED.md)** - Installation and first steps
136
+ - **[Getting Started](docs/GETTING_STARTED.md)** - Install, monitor one model, and check its events
137
+ - **[Migration Guide](docs/MIGRATION_GUIDE.md)** - Installation and the step-by-step move from CRUD to event sourcing
138
+ - **[API Reference](docs/API_REFERENCE.md)** - Configuration options, methods, rake tasks and errors
1017
139
  - **[Architecture Overview](docs/ARCHITECTURE.md)** - System design and components
1018
- - **[Monorepo Structure](docs/MONOREPO.md)** - Repository organization
140
+ - **[Switching Modes](docs/MIGRATION_GUIDE.md#switching-modes)** - Who holds the mode, and how to switch safely (the deploy-time rule)
141
+ - **[Adopting Lyra](docs/MIGRATION_GUIDE.md#adopting-lyra-in-an-existing-application)** - What "non-intrusive" means, the costs, and a checklist for a new codebase
142
+ - **[Performance](docs/PERFORMANCE.md)** - What each mode costs and why (counted in SQL statements per write; throughput being re-measured)
143
+ - **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Common problems
144
+ - **[Workflow Generator](docs/WORKFLOW_GENERATOR.md)** - The Petri net workflow models of Lyra's modes and their verification
145
+ - **[Changelog](CHANGELOG.md)**
1019
146
 
1020
147
  ### Theoretical Foundation
1021
- - **[ORFEAS Framework Overview](docs/ORFEAS_FRAMEWORK_OVERVIEW.md)** - Complete framework description
148
+ - **[ORFEAS Framework Overview](docs/ARCHITECTURE.md#about-orfeas)** - The framework: naming, vision, components and migration path (the opening of ARCHITECTURE.md)
1022
149
  - **[Petri Nets Model](gems/petri_flow/docs/THEORETICAL_MODEL_PETRI_NETS.md)** - P/T nets for verification, CPNs for data modeling
1023
150
  - **[Matrix Analysis Model](gems/petri_flow/docs/THEORETICAL_MODEL_MATRICES.md)** - Linear algebra approach
1024
151
 
@@ -1042,10 +169,26 @@ ORFEAS and Lyra are grounded in peer-reviewed research:
1042
169
 
1043
170
  ### Published Papers
1044
171
 
172
+ **Pantelelis, M., & Kalloniatis, C. (2026).** *From ORM to OEM: Object-to-Events Mapping for the OLEP Era.*
173
+ IEEE Software.
174
+ DOI: [10.1109/MS.2026.3734188](https://doi.org/10.1109/MS.2026.3734188)
175
+
176
+ **Pantelelis, M., & Kalloniatis, C. (2026).** *PAM: A Domain-Specific Language for Specifying Privacy Requirements from Regulation to Runtime.*
177
+ Requirements Engineering, Springer.
178
+ DOI: [10.1007/s00766-026-00469-6](https://doi.org/10.1007/s00766-026-00469-6)
179
+
180
+ **Pantelelis, M., & Kalloniatis, C. (2024).** *Create, Read, Update, Delete: Implications on Security and Privacy Principles regarding GDPR.*
181
+ ARES 2024, 21st International Workshop on Trust, Privacy and Security in the Digital Society (TrustBus), ACM.
182
+ DOI: [10.1145/3664476.3670898](https://doi.org/10.1145/3664476.3670898)
183
+
1045
184
  **Pantelelis, M., & Kalloniatis, C. (2022).** *Mapping CRUD to Events: Towards an object to event-sourcing framework.*
1046
185
  26th Pan-Hellenic Conference on Informatics (PCI 2022).
1047
186
  DOI: [10.1145/3575879.3576006](https://doi.org/10.1145/3575879.3576006)
1048
187
 
188
+ **Pantelelis, M., & Kalloniatis, C. (2022).** *Object Relational Mapping vs. Event-Sourcing: Systematic Review.*
189
+ EGOVIS 2022, Lecture Notes in Computer Science 13429, Springer, pp. 18–31.
190
+ DOI: [10.1007/978-3-031-12673-4_2](https://doi.org/10.1007/978-3-031-12673-4_2)
191
+
1049
192
  ### Research Areas
1050
193
 
1051
194
  - **Event Sourcing Patterns**: Formal models for CRUD-to-event transformation
@@ -1097,12 +240,19 @@ cd lyra
1097
240
  # Install dependencies
1098
241
  bundle install
1099
242
 
1100
- # Run tests
1101
- rake test
243
+ # The engine's tests need PostgreSQL on localhost:5433 (user and password
244
+ # "postgres", database lyra_test; see test/dummy/config/database.yml).
245
+ # docker-compose.yml provides one:
246
+ bundle exec rake docker:start
247
+ docker exec lyra_postgres createdb -U postgres lyra_test
248
+
249
+ # Run the engine's tests, then pam_dsl's and petri_flow's
250
+ bundle exec rake test:all
1102
251
 
1103
- # Build gems
1104
- cd gems/petri_flow && rake build
1105
- cd gems/pam_dsl && rake build
252
+ # Build the gems
253
+ bundle exec rake build
254
+ (cd gems/pam_dsl && bundle exec rake build)
255
+ (cd gems/petri_flow && bundle exec rake build)
1106
256
  ```
1107
257
 
1108
258
  ---
@@ -1123,7 +273,50 @@ If you use this software in academic research, please cite:
1123
273
  }
1124
274
  ```
1125
275
 
1126
- ### Research Paper Citation
276
+ ### Research Paper Citations
277
+
278
+ Lyra's object-to-events mapping, and how its modes compare in cost:
279
+
280
+ ```bibtex
281
+ @article{pantelelis2026oem,
282
+ title={From {ORM} to {OEM}: Object-to-Events Mapping for the {OLEP} Era},
283
+ author={Pantelelis, Michail and Kalloniatis, Christos},
284
+ journal={IEEE Software},
285
+ year={2026},
286
+ doi={10.1109/MS.2026.3734188}
287
+ }
288
+ ```
289
+
290
+ PAM, the privacy DSL (`gems/pam_dsl`), from regulation to runtime:
291
+
292
+ ```bibtex
293
+ @article{pantelelis2026pam,
294
+ title={{PAM}: A Domain-Specific Language for Specifying Privacy Requirements from Regulation to Runtime},
295
+ author={Pantelelis, Michail and Kalloniatis, Christos},
296
+ journal={Requirements Engineering},
297
+ publisher={Springer},
298
+ year={2026},
299
+ doi={10.1007/s00766-026-00469-6}
300
+ }
301
+ ```
302
+
303
+ What each CRUD operation means for GDPR's security and privacy principles, the ground of PAM and Lyra's privacy events:
304
+
305
+ ```bibtex
306
+ @inproceedings{pantelelis2024crud,
307
+ title={Create, Read, Update, Delete: Implications on Security and Privacy Principles regarding {GDPR}},
308
+ author={Pantelelis, Michail and Kalloniatis, Christos},
309
+ booktitle={Proceedings of the 19th International Conference on Availability, Reliability and Security (ARES 2024), 21st International Workshop on Trust, Privacy and Security in the Digital Society (TrustBus)},
310
+ articleno={178},
311
+ pages={1--7},
312
+ publisher={ACM},
313
+ address={Vienna, Austria},
314
+ year={2024},
315
+ doi={10.1145/3664476.3670898}
316
+ }
317
+ ```
318
+
319
+ The CRUD-to-events mapping as first proposed:
1127
320
 
1128
321
  ```bibtex
1129
322
  @inproceedings{pantelelis2022mapping,
@@ -1135,6 +328,22 @@ If you use this software in academic research, please cite:
1135
328
  }
1136
329
  ```
1137
330
 
331
+ The literature review behind it, ORM against Event Sourcing:
332
+
333
+ ```bibtex
334
+ @inproceedings{pantelelis2022egovis,
335
+ title={Object Relational Mapping vs. Event-Sourcing: Systematic Review},
336
+ author={Pantelelis, Michail and Kalloniatis, Christos},
337
+ booktitle={Electronic Government and the Information Systems Perspective (EGOVIS 2022)},
338
+ series={Lecture Notes in Computer Science},
339
+ volume={13429},
340
+ pages={18--31},
341
+ publisher={Springer},
342
+ year={2022},
343
+ doi={10.1007/978-3-031-12673-4_2}
344
+ }
345
+ ```
346
+
1138
347
  ### References
1139
348
 
1140
349
  - [Rails Event Store](https://railseventstore.org/) - Event Store implementation