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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +222 -0
- data/LICENSE +21 -0
- data/README.md +1165 -0
- data/Rakefile +728 -0
- data/app/controllers/lyra/application_controller.rb +23 -0
- data/app/controllers/lyra/dashboard_controller.rb +624 -0
- data/app/controllers/lyra/flow_controller.rb +224 -0
- data/app/controllers/lyra/privacy_controller.rb +182 -0
- data/app/views/lyra/dashboard/audit_trail.html.erb +324 -0
- data/app/views/lyra/dashboard/discrepancies.html.erb +125 -0
- data/app/views/lyra/dashboard/event_graph_view.html.erb +525 -0
- data/app/views/lyra/dashboard/heatmap_view.html.erb +155 -0
- data/app/views/lyra/dashboard/index.html.erb +119 -0
- data/app/views/lyra/dashboard/model_overview.html.erb +115 -0
- data/app/views/lyra/dashboard/projections.html.erb +302 -0
- data/app/views/lyra/dashboard/schema.html.erb +283 -0
- data/app/views/lyra/dashboard/schema_history.html.erb +78 -0
- data/app/views/lyra/dashboard/schema_version.html.erb +340 -0
- data/app/views/lyra/dashboard/verification.html.erb +370 -0
- data/app/views/lyra/flow/crud_mapping.html.erb +125 -0
- data/app/views/lyra/flow/timeline.html.erb +260 -0
- data/app/views/lyra/privacy/pii_detection.html.erb +148 -0
- data/app/views/lyra/privacy/policy.html.erb +188 -0
- data/app/workflows/es_async_mode_workflow.rb +80 -0
- data/app/workflows/es_sync_mode_workflow.rb +64 -0
- data/app/workflows/hijack_mode_workflow.rb +54 -0
- data/app/workflows/lifecycle_workflow.rb +43 -0
- data/app/workflows/monitor_mode_workflow.rb +39 -0
- data/config/privacy_policies.rb +273 -0
- data/config/routes.rb +48 -0
- data/lib/lyra/aggregate.rb +131 -0
- data/lib/lyra/associations/event_aware.rb +225 -0
- data/lib/lyra/command.rb +81 -0
- data/lib/lyra/command_handler.rb +155 -0
- data/lib/lyra/configuration.rb +124 -0
- data/lib/lyra/consistency/read_your_writes.rb +91 -0
- data/lib/lyra/correlation.rb +144 -0
- data/lib/lyra/dual_view.rb +231 -0
- data/lib/lyra/engine.rb +67 -0
- data/lib/lyra/event.rb +71 -0
- data/lib/lyra/event_analyzer.rb +135 -0
- data/lib/lyra/event_flow.rb +449 -0
- data/lib/lyra/event_mapper.rb +106 -0
- data/lib/lyra/event_store_adapter.rb +72 -0
- data/lib/lyra/id_generator.rb +137 -0
- data/lib/lyra/interceptors/association_interceptor.rb +169 -0
- data/lib/lyra/interceptors/crud_interceptor.rb +543 -0
- data/lib/lyra/privacy/gdpr_compliance.rb +161 -0
- data/lib/lyra/privacy/pii_detector.rb +85 -0
- data/lib/lyra/privacy/pii_masker.rb +66 -0
- data/lib/lyra/privacy/policy_integration.rb +253 -0
- data/lib/lyra/projection.rb +94 -0
- data/lib/lyra/projections/async_projection_job.rb +63 -0
- data/lib/lyra/projections/cached_projection.rb +322 -0
- data/lib/lyra/projections/cached_relation.rb +757 -0
- data/lib/lyra/projections/event_store_reader.rb +127 -0
- data/lib/lyra/projections/model_projection.rb +143 -0
- data/lib/lyra/schema/diff.rb +331 -0
- data/lib/lyra/schema/event_class_registrar.rb +63 -0
- data/lib/lyra/schema/generator.rb +190 -0
- data/lib/lyra/schema/reporter.rb +188 -0
- data/lib/lyra/schema/store.rb +156 -0
- data/lib/lyra/schema/validator.rb +100 -0
- data/lib/lyra/strict_data_access.rb +363 -0
- data/lib/lyra/verification/crud_lifecycle_workflow.rb +456 -0
- data/lib/lyra/verification/workflow_generator.rb +540 -0
- data/lib/lyra/version.rb +3 -0
- data/lib/lyra/visualization/activity_heatmap.rb +215 -0
- data/lib/lyra/visualization/event_graph.rb +310 -0
- data/lib/lyra/visualization/timeline.rb +398 -0
- data/lib/lyra.rb +150 -0
- data/lib/tasks/dist.rake +391 -0
- data/lib/tasks/gems.rake +185 -0
- data/lib/tasks/lyra_schema.rake +231 -0
- data/lib/tasks/lyra_workflows.rake +452 -0
- data/lib/tasks/public_release.rake +351 -0
- data/lib/tasks/stats.rake +175 -0
- data/lib/tasks/testbed.rake +479 -0
- data/lib/tasks/version.rake +159 -0
- metadata +221 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.lyra-verification { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
3
|
+
.lyra-verification h1 { color: #1a1a2e; border-bottom: 2px solid #4a4e69; padding-bottom: 10px; }
|
|
4
|
+
.lyra-verification h2 { color: #4a4e69; margin-top: 30px; }
|
|
5
|
+
.lyra-verification h3 { color: #495057; margin-top: 20px; }
|
|
6
|
+
.lyra-verification .breadcrumb { margin-bottom: 20px; font-size: 14px; }
|
|
7
|
+
.lyra-verification .breadcrumb a { color: #4a4e69; text-decoration: none; }
|
|
8
|
+
.lyra-verification .breadcrumb a:hover { text-decoration: underline; }
|
|
9
|
+
.lyra-verification .status-banner { padding: 20px; border-radius: 8px; margin-bottom: 30px; }
|
|
10
|
+
.lyra-verification .status-pass { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
|
|
11
|
+
.lyra-verification .status-fail { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
|
|
12
|
+
.lyra-verification .status-unavailable { background: #fff3cd; border: 1px solid #ffeeba; color: #856404; }
|
|
13
|
+
.lyra-verification .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 15px; margin-bottom: 30px; }
|
|
14
|
+
.lyra-verification .stat-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; }
|
|
15
|
+
.lyra-verification .stat-card .value { font-size: 28px; font-weight: 700; }
|
|
16
|
+
.lyra-verification .stat-card .value.pass { color: #28a745; }
|
|
17
|
+
.lyra-verification .stat-card .value.fail { color: #dc3545; }
|
|
18
|
+
.lyra-verification .stat-card .label { font-size: 13px; color: #6c757d; margin-top: 5px; }
|
|
19
|
+
.lyra-verification .workflow-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
|
|
20
|
+
.lyra-verification .workflow-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
|
|
21
|
+
.lyra-verification .workflow-header h3 { margin: 0; }
|
|
22
|
+
.lyra-verification .workflow-badge { padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: 600; }
|
|
23
|
+
.lyra-verification .badge-pass { background: #d4edda; color: #155724; }
|
|
24
|
+
.lyra-verification .badge-fail { background: #f8d7da; color: #721c24; }
|
|
25
|
+
.lyra-verification .property-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
|
|
26
|
+
.lyra-verification .property { background: #f8f9fa; padding: 12px; border-radius: 6px; }
|
|
27
|
+
.lyra-verification .property .name { font-size: 12px; color: #6c757d; margin-bottom: 4px; }
|
|
28
|
+
.lyra-verification .property .value { font-weight: 600; }
|
|
29
|
+
.lyra-verification .terminal-list { list-style: none; padding: 0; margin: 15px 0 0 0; }
|
|
30
|
+
.lyra-verification .terminal-list li { padding: 8px 12px; border-radius: 4px; margin-bottom: 5px; display: flex; align-items: center; gap: 10px; }
|
|
31
|
+
.lyra-verification .terminal-list .reachable { background: #d4edda; color: #155724; }
|
|
32
|
+
.lyra-verification .terminal-list .unreachable { background: #f8d7da; color: #721c24; }
|
|
33
|
+
.lyra-verification .mermaid-container { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; overflow-x: auto; margin-top: 15px; }
|
|
34
|
+
.lyra-verification .mermaid-container pre { margin: 0; font-size: 12px; }
|
|
35
|
+
.lyra-verification .recommendations { margin-top: 30px; }
|
|
36
|
+
.lyra-verification .recommendation { background: #fff; border-left: 4px solid #4a4e69; padding: 15px; margin-bottom: 10px; }
|
|
37
|
+
.lyra-verification .download-link { display: inline-block; margin-top: 20px; padding: 10px 20px; background: #4a4e69; color: #fff; text-decoration: none; border-radius: 4px; }
|
|
38
|
+
.lyra-verification .download-link:hover { background: #22223b; }
|
|
39
|
+
.lyra-verification .diagram-legend { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 15px 20px; margin: 20px 0; }
|
|
40
|
+
.lyra-verification .diagram-legend h4 { margin: 0 0 12px 0; color: #495057; font-size: 14px; }
|
|
41
|
+
.lyra-verification .legend-items { display: flex; flex-wrap: wrap; gap: 20px; }
|
|
42
|
+
.lyra-verification .legend-item { display: flex; align-items: center; gap: 8px; }
|
|
43
|
+
.lyra-verification .legend-symbol { font-size: 18px; font-weight: bold; color: #4a4e69; background: #f8f9fa; padding: 4px 10px; border-radius: 4px; font-family: monospace; }
|
|
44
|
+
.lyra-verification .legend-label { font-size: 13px; color: #495057; }
|
|
45
|
+
.lyra-verification .legend-desc { font-size: 12px; color: #6c757d; margin-left: 4px; }
|
|
46
|
+
</style>
|
|
47
|
+
|
|
48
|
+
<div class="lyra-verification">
|
|
49
|
+
<div class="breadcrumb">
|
|
50
|
+
<%= link_to "Dashboard", lyra.dashboard_path %> › Formal Verification
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<h1>CRUD→Event Mapping Verification</h1>
|
|
54
|
+
<p style="color: #6c757d; margin-bottom: 20px;">
|
|
55
|
+
Formal verification of Lyra's CRUD→Event mapping using Petri net analysis.
|
|
56
|
+
This verifies that the framework's behavioral contract is sound.
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<% if @petri_flow_available %>
|
|
60
|
+
<% all_valid = @summary[:lifecycle_valid] && @summary[:modes_valid] %>
|
|
61
|
+
<div class="status-banner <%= all_valid ? 'status-pass' : 'status-fail' %>">
|
|
62
|
+
<strong><%= all_valid ? "✓ All Verifications Passed" : "✗ Verification Issues Detected" %></strong>
|
|
63
|
+
<p style="margin: 10px 0 0 0;">
|
|
64
|
+
<% if all_valid %>
|
|
65
|
+
The CRUD→Event mapping is formally correct. All terminal states are reachable and no deadlocks detected.
|
|
66
|
+
<% else %>
|
|
67
|
+
Some verification checks failed. Review the details below.
|
|
68
|
+
<% end %>
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<h2 style="margin-top: 20px;">About Formal Verification</h2>
|
|
73
|
+
<p style="color: #6c757d; font-size: 14px; line-height: 1.6;">
|
|
74
|
+
This verification uses <strong>Petri net analysis</strong> to formally prove properties of Lyra's CRUD→Event mapping:
|
|
75
|
+
</p>
|
|
76
|
+
<ul style="color: #6c757d; font-size: 14px; line-height: 1.8; margin-bottom: 20px;">
|
|
77
|
+
<li><strong>Reachability</strong> - All terminal states can be reached from the initial state</li>
|
|
78
|
+
<li><strong>Boundedness</strong> - The system is 1-bounded (safe), meaning no state explosion</li>
|
|
79
|
+
<li><strong>Liveness</strong> - No deadlocks; operations can always complete</li>
|
|
80
|
+
<li><strong>Soundness</strong> - The workflow correctly models the behavioral contract</li>
|
|
81
|
+
</ul>
|
|
82
|
+
|
|
83
|
+
<div class="stats-grid">
|
|
84
|
+
<div class="stat-card">
|
|
85
|
+
<div class="value <%= @summary[:lifecycle_valid] ? 'pass' : 'fail' %>">
|
|
86
|
+
<%= @summary[:lifecycle_valid] ? "✓" : "✗" %>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="label">Lifecycle Valid</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="stat-card">
|
|
91
|
+
<div class="value <%= @summary[:modes_valid] ? 'pass' : 'fail' %>">
|
|
92
|
+
<%= @summary[:modes_valid] ? "✓" : "✗" %>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="label">Modes Valid</div>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="stat-card">
|
|
97
|
+
<div class="value <%= @summary[:all_terminals_reachable] ? 'pass' : 'fail' %>">
|
|
98
|
+
<%= @summary[:all_terminals_reachable] ? "✓" : "✗" %>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="label">Terminals Reachable</div>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="stat-card">
|
|
103
|
+
<div class="value <%= @summary[:deadlock_free] ? 'pass' : 'fail' %>">
|
|
104
|
+
<%= @summary[:deadlock_free] ? "✓" : "✗" %>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="label">Deadlock Free</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div class="recommendations" style="margin-top: 20px; margin-bottom: 20px;">
|
|
111
|
+
<h3 style="margin-top: 0;">Recommendations</h3>
|
|
112
|
+
<% (@summary[:recommendations] || []).each do |rec| %>
|
|
113
|
+
<div class="recommendation">
|
|
114
|
+
<%= rec %>
|
|
115
|
+
</div>
|
|
116
|
+
<% end %>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="diagram-legend">
|
|
120
|
+
<h4>Diagram Legend — Arc Pattern Labels</h4>
|
|
121
|
+
<div class="legend-items">
|
|
122
|
+
<div class="legend-item">
|
|
123
|
+
<span class="legend-symbol">∥ Target</span>
|
|
124
|
+
<span class="legend-label"><strong>Fork (AND-split)</strong></span>
|
|
125
|
+
<span class="legend-desc">— Parallel execution: all paths activate simultaneously, label shows target</span>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="legend-item">
|
|
128
|
+
<span class="legend-symbol">⊕ → Target</span>
|
|
129
|
+
<span class="legend-label"><strong>Choice (XOR-split)</strong></span>
|
|
130
|
+
<span class="legend-desc">— Exclusive choice: only one path taken, label shows destination</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<h2>Lifecycle Workflow</h2>
|
|
136
|
+
<p style="color: #6c757d; font-size: 14px;">
|
|
137
|
+
Verifies the entity lifecycle: <code>nonexistent → created → persisted → updated → destroyed → deleted</code>
|
|
138
|
+
</p>
|
|
139
|
+
|
|
140
|
+
<% if @lifecycle %>
|
|
141
|
+
<div class="workflow-card">
|
|
142
|
+
<div class="workflow-header">
|
|
143
|
+
<h3><%= @lifecycle[:workflow] %></h3>
|
|
144
|
+
<% lifecycle_valid = @lifecycle[:terminal_reachability].values.all? %>
|
|
145
|
+
<span class="workflow-badge <%= lifecycle_valid ? 'badge-pass' : 'badge-fail' %>">
|
|
146
|
+
<%= lifecycle_valid ? "PASS" : "FAIL" %>
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div class="property-grid">
|
|
151
|
+
<div class="property">
|
|
152
|
+
<div class="name">Reachable States</div>
|
|
153
|
+
<div class="value"><%= @lifecycle[:verification][:reachability][:total_reachable_states] %></div>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="property">
|
|
156
|
+
<div class="name">Terminal States</div>
|
|
157
|
+
<div class="value"><%= @lifecycle[:verification][:reachability][:terminal_states] %></div>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="property">
|
|
160
|
+
<div class="name">1-Bounded (Safe)</div>
|
|
161
|
+
<div class="value"><%= @lifecycle[:verification][:boundedness][:is_safe] ? "Yes" : "No" %></div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="property">
|
|
164
|
+
<div class="name">Deadlock Free</div>
|
|
165
|
+
<div class="value"><%= @lifecycle[:verification][:liveness][:deadlock_free] ? "Yes" : "No" %></div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<h4 style="margin-top: 20px;">Terminal State Reachability</h4>
|
|
170
|
+
<ul class="terminal-list">
|
|
171
|
+
<% @lifecycle[:terminal_reachability].each do |state, reachable| %>
|
|
172
|
+
<li class="<%= reachable ? 'reachable' : 'unreachable' %>">
|
|
173
|
+
<span><%= reachable ? "✓" : "✗" %></span>
|
|
174
|
+
<strong><%= state %></strong>
|
|
175
|
+
<span>- <%= reachable ? "Reachable" : "UNREACHABLE" %></span>
|
|
176
|
+
</li>
|
|
177
|
+
<% end %>
|
|
178
|
+
</ul>
|
|
179
|
+
|
|
180
|
+
<% if @lifecycle[:diagrams] && @lifecycle[:diagrams][:mermaid] %>
|
|
181
|
+
<% mermaid_code = @lifecycle[:diagrams][:mermaid].to_s.gsub(/```mermaid\n?/, '').gsub(/```\s*$/, '').strip %>
|
|
182
|
+
<h4 style="margin-top: 20px;">Petri Net Diagram</h4>
|
|
183
|
+
<div class="mermaid-container">
|
|
184
|
+
<pre class="mermaid"><%= mermaid_code %></pre>
|
|
185
|
+
</div>
|
|
186
|
+
<details style="margin-top: 10px;">
|
|
187
|
+
<summary style="cursor: pointer; color: #6c757d; font-size: 13px;">View raw Mermaid code</summary>
|
|
188
|
+
<pre style="background: #f1f1f1; padding: 10px; margin-top: 5px; font-size: 12px; overflow-x: auto;"><code><%= mermaid_code %></code></pre>
|
|
189
|
+
</details>
|
|
190
|
+
<% end %>
|
|
191
|
+
</div>
|
|
192
|
+
<% end %>
|
|
193
|
+
|
|
194
|
+
<h2>CRUD Mode Verification Workflows</h2>
|
|
195
|
+
<p style="color: #6c757d; font-size: 14px;">
|
|
196
|
+
Verifies that all operating modes (Monitor, Hijack, Event Sourcing) correctly reach completion for each CRUD operation.
|
|
197
|
+
</p>
|
|
198
|
+
|
|
199
|
+
<% if @modes && @modes.any? %>
|
|
200
|
+
<% @modes.each do |operation, mode_data| %>
|
|
201
|
+
<div class="workflow-card">
|
|
202
|
+
<div class="workflow-header">
|
|
203
|
+
<h3><%= mode_data[:workflow] %> <span style="font-size: 14px; color: #6c757d;">(<%= operation.to_s.titleize %>)</span></h3>
|
|
204
|
+
<% mode_valid = mode_data[:terminal_reachability]&.values&.all? %>
|
|
205
|
+
<span class="workflow-badge <%= mode_valid ? 'badge-pass' : 'badge-fail' %>">
|
|
206
|
+
<%= mode_valid ? "PASS" : "FAIL" %>
|
|
207
|
+
</span>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div class="property-grid">
|
|
211
|
+
<div class="property">
|
|
212
|
+
<div class="name">Reachable States</div>
|
|
213
|
+
<div class="value"><%= mode_data.dig(:verification, :reachability, :total_reachable_states) || 'N/A' %></div>
|
|
214
|
+
</div>
|
|
215
|
+
<div class="property">
|
|
216
|
+
<div class="name">1-Bounded (Safe)</div>
|
|
217
|
+
<div class="value"><%= mode_data.dig(:verification, :boundedness, :is_safe) ? "Yes" : "No" %></div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<% if mode_data[:terminal_reachability]&.any? %>
|
|
222
|
+
<h4 style="margin-top: 20px;">Terminal State Reachability</h4>
|
|
223
|
+
<ul class="terminal-list">
|
|
224
|
+
<% mode_data[:terminal_reachability].each do |state, reachable| %>
|
|
225
|
+
<li class="<%= reachable ? 'reachable' : 'unreachable' %>">
|
|
226
|
+
<span><%= reachable ? "✓" : "✗" %></span>
|
|
227
|
+
<strong><%= state %></strong>
|
|
228
|
+
<span>- <%= reachable ? "Reachable" : "UNREACHABLE" %></span>
|
|
229
|
+
</li>
|
|
230
|
+
<% end %>
|
|
231
|
+
</ul>
|
|
232
|
+
<% end %>
|
|
233
|
+
|
|
234
|
+
<% if mode_data[:diagrams] && mode_data[:diagrams][:mermaid] %>
|
|
235
|
+
<% modes_mermaid_code = mode_data[:diagrams][:mermaid].to_s.gsub(/```mermaid\n?/, '').gsub(/```\s*$/, '').strip %>
|
|
236
|
+
<details style="margin-top: 15px;">
|
|
237
|
+
<summary style="cursor: pointer; color: #4a4e69; font-weight: 600;">Show <%= operation.to_s.titleize %> Mode Petri Net Diagram</summary>
|
|
238
|
+
<div class="mermaid-container" style="margin-top: 10px;">
|
|
239
|
+
<pre class="mermaid"><%= modes_mermaid_code %></pre>
|
|
240
|
+
</div>
|
|
241
|
+
<details style="margin-top: 10px;">
|
|
242
|
+
<summary style="cursor: pointer; color: #6c757d; font-size: 13px;">View raw Mermaid code</summary>
|
|
243
|
+
<pre style="background: #f1f1f1; padding: 10px; margin-top: 5px; font-size: 12px; overflow-x: auto;"><code><%= modes_mermaid_code %></code></pre>
|
|
244
|
+
</details>
|
|
245
|
+
</details>
|
|
246
|
+
<% end %>
|
|
247
|
+
</div>
|
|
248
|
+
<% end %>
|
|
249
|
+
<% end %>
|
|
250
|
+
|
|
251
|
+
<% if @generated_workflows.any? %>
|
|
252
|
+
<h2>Generated Mode Workflows</h2>
|
|
253
|
+
<p style="color: #6c757d; font-size: 14px;">
|
|
254
|
+
Individual workflow models for each Lyra operation mode, generated via metaprogramming introspection.
|
|
255
|
+
</p>
|
|
256
|
+
|
|
257
|
+
<% @generated_workflows.each do |key, workflow| %>
|
|
258
|
+
<% next if workflow[:error] %>
|
|
259
|
+
<div class="workflow-card">
|
|
260
|
+
<div class="workflow-header">
|
|
261
|
+
<h3><%= workflow[:workflow] %></h3>
|
|
262
|
+
<% wf_valid = workflow[:terminal_reachability]&.values&.all? %>
|
|
263
|
+
<span class="workflow-badge <%= wf_valid ? 'badge-pass' : 'badge-fail' %>">
|
|
264
|
+
<%= wf_valid ? "PASS" : "FAIL" %>
|
|
265
|
+
</span>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<div class="property-grid">
|
|
269
|
+
<div class="property">
|
|
270
|
+
<div class="name">Reachable States</div>
|
|
271
|
+
<div class="value"><%= workflow.dig(:verification, :reachability, :total_reachable_states) || 'N/A' %></div>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="property">
|
|
274
|
+
<div class="name">Terminal States</div>
|
|
275
|
+
<div class="value"><%= workflow.dig(:verification, :reachability, :terminal_states) || 'N/A' %></div>
|
|
276
|
+
</div>
|
|
277
|
+
<div class="property">
|
|
278
|
+
<div class="name">1-Bounded (Safe)</div>
|
|
279
|
+
<div class="value"><%= workflow.dig(:verification, :boundedness, :is_safe) ? "Yes" : "No" %></div>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="property">
|
|
282
|
+
<div class="name">Source File</div>
|
|
283
|
+
<div class="value" style="font-size: 11px;"><%= File.basename(workflow[:file]) %></div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<% if workflow[:terminal_reachability]&.any? %>
|
|
288
|
+
<h4 style="margin-top: 20px;">Terminal State Reachability</h4>
|
|
289
|
+
<ul class="terminal-list">
|
|
290
|
+
<% workflow[:terminal_reachability].each do |state, reachable| %>
|
|
291
|
+
<li class="<%= reachable ? 'reachable' : 'unreachable' %>">
|
|
292
|
+
<span><%= reachable ? "✓" : "✗" %></span>
|
|
293
|
+
<strong><%= state %></strong>
|
|
294
|
+
<span>- <%= reachable ? "Reachable" : "UNREACHABLE" %></span>
|
|
295
|
+
</li>
|
|
296
|
+
<% end %>
|
|
297
|
+
</ul>
|
|
298
|
+
<% end %>
|
|
299
|
+
|
|
300
|
+
<% if workflow[:diagrams] && workflow[:diagrams][:mermaid] %>
|
|
301
|
+
<% wf_mermaid = workflow[:diagrams][:mermaid].to_s.gsub(/```mermaid\n?/, '').gsub(/```\s*$/, '').strip %>
|
|
302
|
+
<details style="margin-top: 15px;">
|
|
303
|
+
<summary style="cursor: pointer; color: #4a4e69; font-weight: 600;">Show Petri Net Diagram</summary>
|
|
304
|
+
<div class="mermaid-container" style="margin-top: 10px;">
|
|
305
|
+
<pre class="mermaid"><%= wf_mermaid %></pre>
|
|
306
|
+
</div>
|
|
307
|
+
</details>
|
|
308
|
+
<% end %>
|
|
309
|
+
</div>
|
|
310
|
+
<% end %>
|
|
311
|
+
|
|
312
|
+
<% errors = @generated_workflows.select { |k, v| v[:error] } %>
|
|
313
|
+
<% if errors.any? %>
|
|
314
|
+
<div class="status-banner status-fail" style="margin-top: 20px;">
|
|
315
|
+
<strong>Workflow Load Errors</strong>
|
|
316
|
+
<ul style="margin: 10px 0 0 0;">
|
|
317
|
+
<% errors.each do |key, workflow| %>
|
|
318
|
+
<li><strong><%= key %>:</strong> <%= workflow[:error] %></li>
|
|
319
|
+
<% end %>
|
|
320
|
+
</ul>
|
|
321
|
+
</div>
|
|
322
|
+
<% end %>
|
|
323
|
+
<% end %>
|
|
324
|
+
|
|
325
|
+
<a href="<%= verification_data_path(format: :json) %>" class="download-link">Download Full Report (JSON)</a>
|
|
326
|
+
|
|
327
|
+
<% else %>
|
|
328
|
+
<div class="status-banner status-unavailable">
|
|
329
|
+
<strong>PetriFlow Not Available</strong>
|
|
330
|
+
<p style="margin: 10px 0 0 0;">
|
|
331
|
+
Formal verification requires the PetriFlow gem. Add it to your Gemfile:
|
|
332
|
+
</p>
|
|
333
|
+
<pre style="background: #fff; padding: 10px; margin-top: 10px; border-radius: 4px;"><code>gem 'petri_flow', path: 'gems/petri_flow'</code></pre>
|
|
334
|
+
</div>
|
|
335
|
+
<% end %>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<% if @petri_flow_available %>
|
|
339
|
+
<script type="module">
|
|
340
|
+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
|
341
|
+
|
|
342
|
+
mermaid.initialize({
|
|
343
|
+
startOnLoad: false,
|
|
344
|
+
theme: 'neutral',
|
|
345
|
+
flowchart: {
|
|
346
|
+
useMaxWidth: true,
|
|
347
|
+
htmlLabels: true,
|
|
348
|
+
curve: 'basis'
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Render visible diagrams immediately (not inside closed details)
|
|
353
|
+
const visibleDiagrams = document.querySelectorAll('.mermaid-container:not(details .mermaid-container) .mermaid');
|
|
354
|
+
if (visibleDiagrams.length > 0) {
|
|
355
|
+
await mermaid.run({ nodes: Array.from(visibleDiagrams) });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Lazy render diagrams inside <details> when opened
|
|
359
|
+
document.querySelectorAll('details').forEach(details => {
|
|
360
|
+
details.addEventListener('toggle', async function() {
|
|
361
|
+
if (this.open) {
|
|
362
|
+
const diagrams = this.querySelectorAll('.mermaid:not([data-processed])');
|
|
363
|
+
if (diagrams.length > 0) {
|
|
364
|
+
await mermaid.run({ nodes: Array.from(diagrams) });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
</script>
|
|
370
|
+
<% end %>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.lyra-crud-mapping { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
3
|
+
.lyra-crud-mapping h1 { color: #1a1a2e; border-bottom: 2px solid #4a4e69; padding-bottom: 10px; }
|
|
4
|
+
.lyra-crud-mapping h2 { color: #4a4e69; margin-top: 30px; }
|
|
5
|
+
.lyra-crud-mapping .back-link { display: inline-block; margin-bottom: 20px; color: #4a4e69; text-decoration: none; }
|
|
6
|
+
.lyra-crud-mapping .back-link:hover { text-decoration: underline; }
|
|
7
|
+
.lyra-crud-mapping .summary-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; margin: 20px 0; }
|
|
8
|
+
.lyra-crud-mapping .summary-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 15px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
|
9
|
+
.lyra-crud-mapping .summary-card .value { font-size: 28px; font-weight: 700; color: #1a1a2e; }
|
|
10
|
+
.lyra-crud-mapping .summary-card .label { color: #6c757d; font-size: 13px; margin-top: 5px; }
|
|
11
|
+
.lyra-crud-mapping .model-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; margin-bottom: 15px; overflow: hidden; }
|
|
12
|
+
.lyra-crud-mapping .model-header { background: #f8f9fa; padding: 15px 20px; border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; }
|
|
13
|
+
.lyra-crud-mapping .model-header h3 { margin: 0; color: #1a1a2e; font-size: 16px; }
|
|
14
|
+
.lyra-crud-mapping .model-header .total { background: #4a4e69; color: #fff; padding: 4px 12px; border-radius: 12px; font-size: 12px; }
|
|
15
|
+
.lyra-crud-mapping .operations { padding: 15px 20px; display: flex; flex-wrap: wrap; gap: 15px; }
|
|
16
|
+
.lyra-crud-mapping .operation-stat { display: flex; flex-direction: column; align-items: center; padding: 15px 25px; border-radius: 6px; min-width: 100px; }
|
|
17
|
+
.lyra-crud-mapping .operation-stat .count { font-size: 24px; font-weight: 700; }
|
|
18
|
+
.lyra-crud-mapping .operation-stat .label { font-size: 12px; text-transform: uppercase; margin-top: 5px; }
|
|
19
|
+
.lyra-crud-mapping .op-created { background: #d4edda; color: #155724; }
|
|
20
|
+
.lyra-crud-mapping .op-updated { background: #fff3cd; color: #856404; }
|
|
21
|
+
.lyra-crud-mapping .op-destroyed { background: #f8d7da; color: #721c24; }
|
|
22
|
+
.lyra-crud-mapping .empty-state { text-align: center; padding: 40px; color: #6c757d; background: #fff; border: 1px solid #dee2e6; border-radius: 8px; }
|
|
23
|
+
.lyra-crud-mapping .matrix-table { width: 100%; border-collapse: collapse; margin-top: 20px; background: #fff; border-radius: 8px; overflow: hidden; border: 1px solid #dee2e6; }
|
|
24
|
+
.lyra-crud-mapping .matrix-table th { background: #f8f9fa; padding: 12px 15px; text-align: left; border-bottom: 1px solid #dee2e6; font-weight: 600; }
|
|
25
|
+
.lyra-crud-mapping .matrix-table td { padding: 12px 15px; border-bottom: 1px solid #eee; }
|
|
26
|
+
.lyra-crud-mapping .matrix-table tr:last-child td { border-bottom: none; }
|
|
27
|
+
.lyra-crud-mapping .matrix-table .model-name { font-weight: 500; }
|
|
28
|
+
.lyra-crud-mapping .matrix-table .count-cell { text-align: center; }
|
|
29
|
+
.lyra-crud-mapping .matrix-table .count-badge { display: inline-block; min-width: 30px; padding: 4px 8px; border-radius: 4px; font-weight: 600; text-align: center; }
|
|
30
|
+
</style>
|
|
31
|
+
|
|
32
|
+
<div class="lyra-crud-mapping">
|
|
33
|
+
<%= link_to "← Back to Dashboard".html_safe, dashboard_path, class: "back-link" %>
|
|
34
|
+
|
|
35
|
+
<h1>CRUD Mapping</h1>
|
|
36
|
+
<p style="color: #6c757d;">Shows how CRUD operations map to events in the event store.</p>
|
|
37
|
+
|
|
38
|
+
<h2 style="margin-top: 0;">Overview</h2>
|
|
39
|
+
<p style="color: #6c757d; font-size: 13px; margin-bottom: 15px;">
|
|
40
|
+
Total event counts by operation type. This helps understand the overall activity pattern in your application.
|
|
41
|
+
</p>
|
|
42
|
+
<div class="summary-cards">
|
|
43
|
+
<div class="summary-card">
|
|
44
|
+
<div class="value"><%= @total_events %></div>
|
|
45
|
+
<div class="label">Total Events</div>
|
|
46
|
+
</div>
|
|
47
|
+
<% (@operations_summary || {}).each do |op, count| %>
|
|
48
|
+
<div class="summary-card">
|
|
49
|
+
<div class="value"><%= count %></div>
|
|
50
|
+
<div class="label"><%= op.to_s.titleize %></div>
|
|
51
|
+
</div>
|
|
52
|
+
<% end %>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<h2>CRUD Matrix by Model</h2>
|
|
56
|
+
<p style="color: #6c757d; font-size: 13px; margin-bottom: 15px;">
|
|
57
|
+
Tabular view of all CRUD operations across models. Useful for identifying which models have the most activity
|
|
58
|
+
and understanding the create/update/delete ratio.
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
<% if @models_summary&.any? %>
|
|
62
|
+
<table class="matrix-table">
|
|
63
|
+
<thead>
|
|
64
|
+
<tr>
|
|
65
|
+
<th>Model</th>
|
|
66
|
+
<th class="count-cell">Created</th>
|
|
67
|
+
<th class="count-cell">Updated</th>
|
|
68
|
+
<th class="count-cell">Destroyed</th>
|
|
69
|
+
<th class="count-cell">Total</th>
|
|
70
|
+
</tr>
|
|
71
|
+
</thead>
|
|
72
|
+
<tbody>
|
|
73
|
+
<% @models_summary.each do |model, data| %>
|
|
74
|
+
<tr>
|
|
75
|
+
<td class="model-name"><%= model %></td>
|
|
76
|
+
<td class="count-cell">
|
|
77
|
+
<span class="count-badge op-created"><%= data[:operations][:created] || 0 %></span>
|
|
78
|
+
</td>
|
|
79
|
+
<td class="count-cell">
|
|
80
|
+
<span class="count-badge op-updated"><%= data[:operations][:updated] || 0 %></span>
|
|
81
|
+
</td>
|
|
82
|
+
<td class="count-cell">
|
|
83
|
+
<span class="count-badge op-destroyed"><%= data[:operations][:destroyed] || 0 %></span>
|
|
84
|
+
</td>
|
|
85
|
+
<td class="count-cell">
|
|
86
|
+
<strong><%= data[:total] %></strong>
|
|
87
|
+
</td>
|
|
88
|
+
</tr>
|
|
89
|
+
<% end %>
|
|
90
|
+
</tbody>
|
|
91
|
+
</table>
|
|
92
|
+
<% else %>
|
|
93
|
+
<div class="empty-state">
|
|
94
|
+
<p>No CRUD events found.</p>
|
|
95
|
+
<p>Events will appear here as operations are performed on monitored models.</p>
|
|
96
|
+
</div>
|
|
97
|
+
<% end %>
|
|
98
|
+
|
|
99
|
+
<h2>Events by Model</h2>
|
|
100
|
+
<p style="color: #6c757d; font-size: 13px; margin-bottom: 15px;">
|
|
101
|
+
Visual cards showing operation distribution per model. Color-coded badges indicate operation types:
|
|
102
|
+
<span style="background: #d4edda; padding: 2px 8px; border-radius: 4px; color: #155724;">Created</span>
|
|
103
|
+
<span style="background: #fff3cd; padding: 2px 8px; border-radius: 4px; color: #856404;">Updated</span>
|
|
104
|
+
<span style="background: #f8d7da; padding: 2px 8px; border-radius: 4px; color: #721c24;">Destroyed</span>
|
|
105
|
+
</p>
|
|
106
|
+
|
|
107
|
+
<% if @models_summary&.any? %>
|
|
108
|
+
<% @models_summary.each do |model, data| %>
|
|
109
|
+
<div class="model-card">
|
|
110
|
+
<div class="model-header">
|
|
111
|
+
<h3><%= model %></h3>
|
|
112
|
+
<span class="total"><%= data[:total] %> event<%= data[:total] != 1 ? 's' : '' %></span>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="operations">
|
|
115
|
+
<% data[:operations].each do |op, count| %>
|
|
116
|
+
<div class="operation-stat op-<%= op %>">
|
|
117
|
+
<span class="count"><%= count %></span>
|
|
118
|
+
<span class="label"><%= op %></span>
|
|
119
|
+
</div>
|
|
120
|
+
<% end %>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
<% end %>
|
|
124
|
+
<% end %>
|
|
125
|
+
</div>
|