open-loam 0.1.0 → 0.2.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 (273) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -8
  3. data/LICENSE +1 -1
  4. data/README.md +24 -24
  5. data/app/jobs/{loam → open_loam}/custom_field_reindex_job.rb +6 -6
  6. data/app/jobs/{loam → open_loam}/event_delivery_job.rb +8 -8
  7. data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
  8. data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +7 -7
  9. data/app/models/{loam → open_loam}/api_token.rb +7 -7
  10. data/app/models/{loam → open_loam}/audit_record.rb +3 -3
  11. data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
  12. data/app/models/{loam → open_loam}/business_rule.rb +10 -10
  13. data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
  14. data/app/models/{loam → open_loam}/comment.rb +9 -9
  15. data/app/models/{loam → open_loam}/config.rb +7 -7
  16. data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
  17. data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
  18. data/app/models/open_loam/dictionary.rb +20 -0
  19. data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
  20. data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
  21. data/app/models/{loam → open_loam}/field_definition.rb +8 -8
  22. data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
  23. data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
  24. data/app/models/{loam → open_loam}/membership.rb +6 -6
  25. data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
  26. data/app/models/{loam → open_loam}/notification.rb +10 -10
  27. data/app/models/{loam → open_loam}/pending_action.rb +23 -23
  28. data/app/models/{loam → open_loam}/perspective.rb +5 -5
  29. data/app/models/{loam → open_loam}/progress_job.rb +7 -7
  30. data/app/models/{loam → open_loam}/record_lock.rb +5 -5
  31. data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
  32. data/app/models/open_loam/search_token.rb +9 -0
  33. data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
  34. data/app/models/{loam → open_loam}/sso_provider.rb +8 -8
  35. data/app/models/open_loam/tenant.rb +20 -0
  36. data/app/models/{loam → open_loam}/translation.rb +5 -5
  37. data/app/models/{loam → open_loam}/webhook_endpoint.rb +6 -6
  38. data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
  39. data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
  40. data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +2 -2
  41. data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
  42. data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +1 -1
  43. data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +21 -21
  44. data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
  45. data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
  46. data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
  47. data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
  48. data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +4 -4
  49. data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
  50. data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
  51. data/lib/generators/open_loam/entity/templates/views/index.html.erb +90 -0
  52. data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
  53. data/lib/generators/open_loam/entity/templates/views/show.html.erb +36 -0
  54. data/lib/generators/{loam → open_loam}/install/install_generator.rb +47 -47
  55. data/lib/generators/open_loam/install/templates/AGENTS.md +341 -0
  56. data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
  57. data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
  58. data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_controller.rb +1 -1
  59. data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +38 -38
  60. data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
  61. data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
  62. data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
  63. data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
  64. data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +10 -0
  65. data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
  66. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
  67. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
  68. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
  69. data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
  70. data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
  71. data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +5 -5
  72. data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
  73. data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
  74. data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
  75. data/lib/generators/{loam → open_loam}/install/templates/admin/history_controller.rb +7 -7
  76. data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +1 -1
  77. data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +12 -12
  78. data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +6 -6
  79. data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -2
  80. data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
  81. data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +6 -6
  82. data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +3 -3
  83. data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
  84. data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
  85. data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
  86. data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +7 -7
  87. data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
  88. data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +19 -0
  89. data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
  90. data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
  91. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
  92. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
  93. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
  94. data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +5 -5
  95. data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +32 -32
  96. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
  97. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
  98. data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +5 -5
  99. data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
  100. data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
  101. data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
  102. data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
  103. data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
  104. data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
  105. data/lib/generators/{loam → open_loam}/install/templates/api_base_controller.rb +15 -15
  106. data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
  107. data/lib/generators/{loam → open_loam}/install/templates/import_job.rb +7 -7
  108. data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
  109. data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +74 -74
  110. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
  111. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
  112. data/lib/generators/{loam/install/templates/migrations/create_loam_auth_attempts.rb → open_loam/install/templates/migrations/create_open_loam_auth_attempts.rb} +3 -3
  113. data/lib/generators/{loam/install/templates/migrations/create_loam_business_rules.rb → open_loam/install/templates/migrations/create_open_loam_business_rules.rb} +10 -10
  114. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
  115. data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
  116. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
  117. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
  118. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
  119. data/lib/generators/{loam/install/templates/migrations/create_loam_dictionary_entries.rb → open_loam/install/templates/migrations/create_open_loam_dictionary_entries.rb} +6 -6
  120. data/lib/generators/{loam/install/templates/migrations/create_loam_event_deliveries.rb → open_loam/install/templates/migrations/create_open_loam_event_deliveries.rb} +5 -5
  121. data/lib/generators/{loam/install/templates/migrations/create_loam_field_definitions.rb → open_loam/install/templates/migrations/create_open_loam_field_definitions.rb} +4 -4
  122. data/lib/generators/{loam/install/templates/migrations/create_loam_inbound_webhooks.rb → open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb} +9 -9
  123. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
  124. data/lib/generators/{loam/install/templates/migrations/create_loam_mfa_credentials.rb → open_loam/install/templates/migrations/create_open_loam_mfa_credentials.rb} +4 -4
  125. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
  126. data/lib/generators/{loam/install/templates/migrations/create_loam_pending_actions.rb → open_loam/install/templates/migrations/create_open_loam_pending_actions.rb} +9 -9
  127. data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
  128. data/lib/generators/{loam/install/templates/migrations/create_loam_progress_jobs.rb → open_loam/install/templates/migrations/create_open_loam_progress_jobs.rb} +5 -5
  129. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
  130. data/lib/generators/{loam/install/templates/migrations/create_loam_scheduled_jobs.rb → open_loam/install/templates/migrations/create_open_loam_scheduled_jobs.rb} +5 -5
  131. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
  132. data/lib/generators/{loam/install/templates/migrations/create_loam_sso_providers.rb → open_loam/install/templates/migrations/create_open_loam_sso_providers.rb} +12 -12
  133. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
  134. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
  135. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
  136. data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
  137. data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
  138. data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
  139. data/lib/open-loam.rb +2 -2
  140. data/lib/{loam → open_loam}/attachable.rb +4 -4
  141. data/lib/{loam → open_loam}/auditable.rb +23 -23
  142. data/lib/{loam → open_loam}/auth_throttle.rb +10 -10
  143. data/lib/{loam → open_loam}/base32.rb +1 -1
  144. data/lib/{loam → open_loam}/bulk.rb +7 -7
  145. data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
  146. data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
  147. data/lib/{loam → open_loam}/business_rules.rb +19 -19
  148. data/lib/{loam → open_loam}/commentable.rb +6 -6
  149. data/lib/{loam → open_loam}/configs.rb +19 -19
  150. data/lib/{loam → open_loam}/cron.rb +2 -2
  151. data/lib/{loam → open_loam}/csv.rb +2 -2
  152. data/lib/{loam → open_loam}/current.rb +5 -5
  153. data/lib/{loam → open_loam}/custom_field_index.rb +23 -23
  154. data/lib/{loam → open_loam}/custom_fields.rb +17 -17
  155. data/lib/{loam → open_loam}/dashboard.rb +5 -5
  156. data/lib/{loam → open_loam}/dictionaries.rb +12 -12
  157. data/lib/{loam → open_loam}/durable_events.rb +19 -19
  158. data/lib/{loam → open_loam}/encryptable.rb +33 -33
  159. data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
  160. data/lib/{loam → open_loam}/encryption/key_provider.rb +3 -3
  161. data/lib/{loam → open_loam}/encryption.rb +11 -11
  162. data/lib/open_loam/engine.rb +57 -0
  163. data/lib/{loam → open_loam}/enrichers.rb +5 -5
  164. data/lib/{loam → open_loam}/errors.rb +10 -10
  165. data/lib/{loam → open_loam}/eval.rb +2 -2
  166. data/lib/{loam → open_loam}/event_stream.rb +14 -14
  167. data/lib/{loam → open_loam}/eventful.rb +12 -12
  168. data/lib/{loam → open_loam}/events.rb +5 -5
  169. data/lib/{loam → open_loam}/export.rb +8 -8
  170. data/lib/{loam → open_loam}/features.rb +15 -15
  171. data/lib/{loam → open_loam}/generated_key.rb +6 -6
  172. data/lib/{loam → open_loam}/import.rb +12 -12
  173. data/lib/{loam → open_loam}/inbound_webhooks.rb +7 -7
  174. data/lib/{loam → open_loam}/lifecycle.rb +25 -25
  175. data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
  176. data/lib/{loam → open_loam}/mcp/server.rb +9 -9
  177. data/lib/{loam → open_loam}/mcp.rb +21 -21
  178. data/lib/{loam → open_loam}/notifications.rb +5 -5
  179. data/lib/{loam → open_loam}/open_api.rb +16 -16
  180. data/lib/{loam → open_loam}/overrides.rb +10 -10
  181. data/lib/{loam → open_loam}/pending_actions.rb +9 -9
  182. data/lib/{loam → open_loam}/permissions.rb +8 -8
  183. data/lib/{loam → open_loam}/perspectives.rb +9 -9
  184. data/lib/{loam → open_loam}/policy.rb +6 -6
  185. data/lib/{loam → open_loam}/progress.rb +6 -6
  186. data/lib/{loam → open_loam}/record_locks.rb +9 -9
  187. data/lib/{loam → open_loam}/scheduler.rb +20 -20
  188. data/lib/{loam → open_loam}/search/driver.rb +3 -3
  189. data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
  190. data/lib/{loam → open_loam}/search/token_driver.rb +15 -15
  191. data/lib/{loam → open_loam}/search.rb +5 -5
  192. data/lib/{loam → open_loam}/searchable.rb +22 -22
  193. data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
  194. data/lib/{loam → open_loam}/sso/claims.rb +1 -1
  195. data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
  196. data/lib/{loam → open_loam}/sso/http_client.rb +3 -3
  197. data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
  198. data/lib/{loam → open_loam}/sso.rb +17 -17
  199. data/lib/{loam → open_loam}/telemetry.rb +6 -6
  200. data/lib/{loam → open_loam}/tenant_record.rb +10 -10
  201. data/lib/{loam → open_loam}/test_helpers.rb +4 -4
  202. data/lib/{loam → open_loam}/totp.rb +3 -3
  203. data/lib/{loam → open_loam}/translatable.rb +24 -24
  204. data/lib/{loam → open_loam}/undo.rb +11 -11
  205. data/lib/open_loam/version.rb +3 -0
  206. data/lib/{loam → open_loam}/webhooks.rb +4 -4
  207. data/lib/{loam → open_loam}/widgets.rb +9 -9
  208. data/lib/{loam → open_loam}/workflow.rb +45 -45
  209. data/lib/open_loam.rb +122 -0
  210. data/lib/tasks/{loam.rake → open_loam.rake} +43 -43
  211. data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
  212. data/lib/tasks/open_loam_mcp.rake +20 -0
  213. metadata +242 -242
  214. data/app/models/loam/dictionary.rb +0 -20
  215. data/app/models/loam/search_token.rb +0 -9
  216. data/app/models/loam/tenant.rb +0 -20
  217. data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
  218. data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
  219. data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
  220. data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
  221. data/lib/generators/loam/install/templates/AGENTS.md +0 -341
  222. data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
  223. data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
  224. data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
  225. data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
  226. data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
  227. data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
  228. data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
  229. data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
  230. data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
  231. data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
  232. data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
  233. data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
  234. data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
  235. data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
  236. data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
  237. data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
  238. data/lib/loam/engine.rb +0 -57
  239. data/lib/loam/version.rb +0 -3
  240. data/lib/loam.rb +0 -122
  241. data/lib/tasks/loam_mcp.rake +0 -20
  242. /data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +0 -0
  243. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
  244. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
  245. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
  246. /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
  247. /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
  248. /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
  249. /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
  250. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
  251. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
  252. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
  253. /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
  254. /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
  255. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
  256. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
  257. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
  258. /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
  259. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
  260. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
  261. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
  262. /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
  263. /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
  264. /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
  265. /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
  266. /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
  267. /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
  268. /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
  269. /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
  270. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
  271. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +0 -0
  272. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
  273. /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
@@ -1,15 +1,15 @@
1
- module Loam
2
- # Durable (persistent) event subscribers — the twin of Loam::Events.subscribe,
1
+ module OpenLoam
2
+ # Durable (persistent) event subscribers — the twin of OpenLoam::Events.subscribe,
3
3
  # and the formal contract L-706 pins down.
4
4
  #
5
5
  # THE CONTRACT
6
- # * Ephemeral — Loam::Events.subscribe(&block): runs INLINE in the publisher's
6
+ # * Ephemeral — OpenLoam::Events.subscribe(&block): runs INLINE in the publisher's
7
7
  # thread, synchronously, best-effort. An exception in the block PROPAGATES
8
8
  # into whatever published the event. No persistence, no retry. Right for
9
9
  # cheap in-process fan-out (the webhook dispatcher enqueues its own jobs
10
10
  # this way).
11
- # * Persistent — Loam::DurableEvents.register(...): publishing commits a
12
- # Loam::EventDelivery row in the event's tenant, then hands the handler to a
11
+ # * Persistent — OpenLoam::DurableEvents.register(...): publishing commits a
12
+ # OpenLoam::EventDelivery row in the event's tenant, then hands the handler to a
13
13
  # background job. The handler's exception is CONTAINED in the job; delivery
14
14
  # is retried with backoff and, past MAX_ATTEMPTS, parked as `dead` for an
15
15
  # operator (Admin::EventDeliveriesController).
@@ -29,10 +29,10 @@ module Loam
29
29
  MAX_ATTEMPTS = 5
30
30
  # Backoff (seconds) indexed by attempt number; the last value is the cap.
31
31
  BACKOFF = [ 0, 60, 300, 1800, 7200 ].freeze
32
- SWEEP_KEY = "loam_event_redelivery_sweep".freeze
32
+ SWEEP_KEY = "open_loam_event_redelivery_sweep".freeze
33
33
 
34
34
  class << self
35
- # --- declarative registry (mirrors Loam::Scheduler.register) ---
35
+ # --- declarative registry (mirrors OpenLoam::Scheduler.register) ---
36
36
 
37
37
  # key: a stable identifier stored on every delivery row it produces.
38
38
  # to: an event name ("billing.invoice.paid") or a domain prefix
@@ -51,7 +51,7 @@ module Loam
51
51
  end
52
52
 
53
53
  def subscribers_for(event_name)
54
- registered.select { |entry| Loam::Events.pattern_matches?(entry[:pattern], event_name) }
54
+ registered.select { |entry| OpenLoam::Events.pattern_matches?(entry[:pattern], event_name) }
55
55
  end
56
56
 
57
57
  def handler_for(key)
@@ -59,19 +59,19 @@ module Loam
59
59
  entry && resolve(entry[:handler])
60
60
  end
61
61
 
62
- # --- wiring (mirrors Loam::Webhooks.subscribe!) ---
62
+ # --- wiring (mirrors OpenLoam::Webhooks.subscribe!) ---
63
63
 
64
- # Wired once from Loam::Engine. Idempotent: subscribing twice would persist
64
+ # Wired once from OpenLoam::Engine. Idempotent: subscribing twice would persist
65
65
  # every event twice.
66
66
  def subscribe!
67
- @subscription ||= Loam::Events.subscribe_all { |event_name, payload| capture(event_name, payload) }
67
+ @subscription ||= OpenLoam::Events.subscribe_all { |event_name, payload| capture(event_name, payload) }
68
68
  end
69
69
 
70
70
  # Persist a delivery row per matching durable subscriber, in the event's
71
71
  # tenant, then nudge a job per row. The ROW is the durable record; the job
72
72
  # is only the accelerator (the sweep redelivers rows whose job was lost).
73
73
  def capture(event_name, payload)
74
- tenant = Loam::Tenant.find_by(id: payload[:tenant_id])
74
+ tenant = OpenLoam::Tenant.find_by(id: payload[:tenant_id])
75
75
  return if tenant.nil? # nil-tenant events are not durably delivered (as Webhooks.dispatch)
76
76
 
77
77
  matches = subscribers_for(event_name)
@@ -81,18 +81,18 @@ module Loam
81
81
  # scalars by convention, never records (same rule as the webhook path).
82
82
  deliverable = payload.transform_keys(&:to_s)
83
83
 
84
- Loam.as_tenant(tenant) do
84
+ OpenLoam.as_tenant(tenant) do
85
85
  matches.each do |sub|
86
- delivery = Loam::EventDelivery.create!(
86
+ delivery = OpenLoam::EventDelivery.create!(
87
87
  subscriber_key: sub[:key], event_name: event_name.to_s,
88
88
  payload: deliverable, status: "pending", attempts: 0
89
89
  )
90
- Loam::EventDeliveryJob.perform_later(tenant.id, delivery.id)
90
+ OpenLoam::EventDeliveryJob.perform_later(tenant.id, delivery.id)
91
91
  end
92
92
  end
93
93
  end
94
94
 
95
- # --- delivery (called by Loam::EventDeliveryJob) ---
95
+ # --- delivery (called by OpenLoam::EventDeliveryJob) ---
96
96
 
97
97
  # Run one delivery and advance the row's state. Manual, ROW-STATE retries —
98
98
  # deliberately NOT ActiveJob retry_on, which keeps retry state in the queue
@@ -102,7 +102,7 @@ module Loam
102
102
  return unless delivery.status == "pending"
103
103
  return if delivery.next_attempt_at && delivery.next_attempt_at > now # duplicate nudge, backoff not elapsed
104
104
 
105
- Loam::Telemetry.span("durable_event_delivery",
105
+ OpenLoam::Telemetry.span("durable_event_delivery",
106
106
  subscriber_key: delivery.subscriber_key, event_name: delivery.event_name) do
107
107
  run_delivery(delivery, now)
108
108
  end
@@ -136,8 +136,8 @@ module Loam
136
136
  # no cross-tenant scan is needed.
137
137
  def redeliver_stuck(now: Time.current, limit: 500)
138
138
  count = 0
139
- Loam::EventDelivery.due(now).limit(limit).find_each do |delivery|
140
- Loam::EventDeliveryJob.perform_later(delivery.tenant_id, delivery.id)
139
+ OpenLoam::EventDelivery.due(now).limit(limit).find_each do |delivery|
140
+ OpenLoam::EventDeliveryJob.perform_later(delivery.tenant_id, delivery.id)
141
141
  count += 1
142
142
  end
143
143
  count
@@ -1,8 +1,8 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Field-level encryption at rest, keyed per tenant. Declare it on a model:
3
3
  #
4
- # class Patient < Loam::TenantRecord
5
- # include Loam::Encryptable
4
+ # class Patient < OpenLoam::TenantRecord
5
+ # include OpenLoam::Encryptable
6
6
  # encrypts :ssn # encrypted at rest, not searchable
7
7
  # encrypts :email, searchable: true # + a blind index for exact-match lookup
8
8
  # end
@@ -11,7 +11,7 @@ module Loam
11
11
  # patient.ssn # => "078-05-1120" (decrypted on read)
12
12
  # Patient.find_by_email("a@b.com") # exact match via the blind index
13
13
  #
14
- # The value is sealed with the CURRENT tenant's key (Loam.tenant!), never the
14
+ # The value is sealed with the CURRENT tenant's key (OpenLoam.tenant!), never the
15
15
  # record's stored tenant_id — so a read in the wrong tenant's context fails the
16
16
  # GCM auth tag instead of quietly decrypting another tenant's data. Reading or
17
17
  # writing an encrypted field with no tenant in context raises
@@ -24,13 +24,13 @@ module Loam
24
24
  extend ActiveSupport::Concern
25
25
 
26
26
  included do
27
- class_attribute :loam_encrypted_attributes, default: [].freeze, instance_writer: false
28
- class_attribute :loam_searchable_encrypted_attributes, default: [].freeze, instance_writer: false
27
+ class_attribute :open_loam_encrypted_attributes, default: [].freeze, instance_writer: false
28
+ class_attribute :open_loam_searchable_encrypted_attributes, default: [].freeze, instance_writer: false
29
29
  end
30
30
 
31
31
  class_methods do
32
32
  # `scope:` chooses whose key seals the field. `:tenant` (default) keys off
33
- # Loam.tenant! — right for entity data. A Proc `->(record) { "user/#{...}" }`
33
+ # OpenLoam.tenant! — right for entity data. A Proc `->(record) { "user/#{...}" }`
34
34
  # keys off something else, for genuinely non-tenant data (an MFA secret
35
35
  # belongs to the person and must decrypt in any tenant, and at login before
36
36
  # a tenant is chosen). Non-tenant scopes cannot be `searchable`.
@@ -38,14 +38,14 @@ module Loam
38
38
  name = name.to_s
39
39
 
40
40
  if searchable && scope != :tenant
41
- raise Loam::Error, "#{self.name}: `#{name}` cannot be both `searchable` and non-tenant-scoped."
41
+ raise OpenLoam::Error, "#{self.name}: `#{name}` cannot be both `searchable` and non-tenant-scoped."
42
42
  end
43
43
 
44
44
  # Encrypted ciphertext is meaningless to a LIKE scan, so the two are a
45
45
  # contradiction. Caught whichever declaration comes second (Searchable
46
46
  # runs the mirror check), so order in the model does not matter.
47
- if respond_to?(:loam_searchable_columns) && loam_searchable_columns.include?(name)
48
- raise Loam::Error,
47
+ if respond_to?(:open_loam_searchable_columns) && open_loam_searchable_columns.include?(name)
48
+ raise OpenLoam::Error,
49
49
  "#{self.name}: `#{name}` is declared both `searchable_by` (LIKE) and `encrypts` — " \
50
50
  "ciphertext cannot be LIKE-searched. Drop it from `searchable_by` and use " \
51
51
  "`encrypts :#{name}, searchable: true` for exact-match lookup instead."
@@ -54,41 +54,41 @@ module Loam
54
54
  # A translation row would store the field's PLAINTEXT — recreating the
55
55
  # leak encryption closes. Caught whichever declaration comes second
56
56
  # (Translatable runs the mirror check), so model order does not matter.
57
- if respond_to?(:loam_translatable_attributes) && loam_translatable_attributes.include?(name)
58
- raise Loam::Error,
57
+ if respond_to?(:open_loam_translatable_attributes) && open_loam_translatable_attributes.include?(name)
58
+ raise OpenLoam::Error,
59
59
  "#{self.name}: `#{name}` is declared both `translates` and `encrypts` — a translation " \
60
60
  "would store plaintext. Encrypted data is not translatable."
61
61
  end
62
62
 
63
- self.loam_encrypted_attributes = (loam_encrypted_attributes + [name]).freeze
64
- self.loam_searchable_encrypted_attributes = (loam_searchable_encrypted_attributes + [name]).freeze if searchable
63
+ self.open_loam_encrypted_attributes = (open_loam_encrypted_attributes + [name]).freeze
64
+ self.open_loam_searchable_encrypted_attributes = (open_loam_searchable_encrypted_attributes + [name]).freeze if searchable
65
65
 
66
- include loam_encryption_reader_writer(name, searchable, scope)
67
- define_loam_blind_index_finder(name) if searchable
66
+ include open_loam_encryption_reader_writer(name, searchable, scope)
67
+ define_open_loam_blind_index_finder(name) if searchable
68
68
  end
69
69
 
70
- # Reader/writer live in their own module (the Loam::Workflow precedent) so
70
+ # Reader/writer live in their own module (the OpenLoam::Workflow precedent) so
71
71
  # an app can override and still call `super`, and so they sit ABOVE Active
72
72
  # Record's generated attribute methods in the ancestor chain and win.
73
- def loam_encryption_reader_writer(name, searchable, scope)
73
+ def open_loam_encryption_reader_writer(name, searchable, scope)
74
74
  hash_column = "#{name}_hash"
75
75
 
76
76
  Module.new do
77
77
  define_method(name) do
78
- resolved = loam_encryption_scope(scope)
79
- aad = Loam::Encryption.aad(resolved, self.class.table_name, name)
80
- Loam::Encryption.decrypt_scoped(read_attribute(name), resolved, aad: aad)
78
+ resolved = open_loam_encryption_scope(scope)
79
+ aad = OpenLoam::Encryption.aad(resolved, self.class.table_name, name)
80
+ OpenLoam::Encryption.decrypt_scoped(read_attribute(name), resolved, aad: aad)
81
81
  end
82
82
 
83
83
  define_method("#{name}=") do |value|
84
- resolved = loam_encryption_scope(scope)
84
+ resolved = open_loam_encryption_scope(scope)
85
85
  # Bind this ciphertext to its (scope, table, column) so it can't be
86
86
  # transplanted to another column/table/tenant (v2 AAD).
87
- aad = Loam::Encryption.aad(resolved, self.class.table_name, name)
88
- write_attribute(name, Loam::Encryption.encrypt_scoped(value, resolved, aad: aad))
87
+ aad = OpenLoam::Encryption.aad(resolved, self.class.table_name, name)
88
+ write_attribute(name, OpenLoam::Encryption.encrypt_scoped(value, resolved, aad: aad))
89
89
  # The blind index tracks the ciphertext column: rewrite it in the
90
90
  # same breath, so an exact-match lookup can never go stale.
91
- write_attribute(hash_column, Loam::Encryption.blind_index_scoped(value, resolved)) if searchable
91
+ write_attribute(hash_column, OpenLoam::Encryption.blind_index_scoped(value, resolved)) if searchable
92
92
  end
93
93
  end
94
94
  end
@@ -97,11 +97,11 @@ module Loam
97
97
  # deliberately shadow Active Record's dynamic `find_by_<attr>`: the obvious
98
98
  # call must hash-and-compare, never match a plaintext query against the
99
99
  # ciphertext column (which would silently find nothing).
100
- def define_loam_blind_index_finder(name)
100
+ def define_open_loam_blind_index_finder(name)
101
101
  hash_column = "#{name}_hash"
102
102
 
103
103
  define_singleton_method("where_#{name}") do |value|
104
- where(hash_column => Loam::Encryption.blind_index(value, Loam.tenant!.id))
104
+ where(hash_column => OpenLoam::Encryption.blind_index(value, OpenLoam.tenant!.id))
105
105
  end
106
106
 
107
107
  define_singleton_method("find_by_#{name}") do |value|
@@ -117,11 +117,11 @@ module Loam
117
117
  # same safety property as an entity write); a Proc computes it from the
118
118
  # record. A blank or `.../`-terminated result (e.g. a nil user_id) raises
119
119
  # rather than deriving a degenerate shared key.
120
- def loam_encryption_scope(scope)
121
- resolved = scope == :tenant ? "tenant/#{Loam.tenant!.id}" : scope.call(self).to_s
120
+ def open_loam_encryption_scope(scope)
121
+ resolved = scope == :tenant ? "tenant/#{OpenLoam.tenant!.id}" : scope.call(self).to_s
122
122
 
123
123
  if resolved.strip.empty? || resolved.end_with?("/")
124
- raise Loam::Encryption::Error, "#{self.class}: cannot derive an encryption key from a blank scope (#{resolved.inspect})"
124
+ raise OpenLoam::Encryption::Error, "#{self.class}: cannot derive an encryption key from a blank scope (#{resolved.inspect})"
125
125
  end
126
126
  resolved
127
127
  end
@@ -134,9 +134,9 @@ module Loam
134
134
  # version tag lets old and new ciphertext coexist while this runs across a
135
135
  # tenant's records, so rotation is a lazy re-encrypt, not a stop-the-world
136
136
  # migration. Writes an ordinary audited "[encrypted]" update — see
137
- # lib/tasks/loam.rake (loam:encryption:rotate).
138
- def loam_reencrypt!
139
- self.class.loam_encrypted_attributes.each do |name|
137
+ # lib/tasks/open_loam.rake (open_loam:encryption:rotate).
138
+ def open_loam_reencrypt!
139
+ self.class.open_loam_encrypted_attributes.each do |name|
140
140
  public_send("#{name}=", public_send(name))
141
141
  end
142
142
  save!
@@ -1,4 +1,4 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Encryption
3
3
  # AES-256-GCM sealing. GCM is *authenticated* encryption: the 16-byte auth
4
4
  # tag turns tampering — or decrypting with the wrong key — into a loud
@@ -13,7 +13,7 @@ module Loam
13
13
  # v2 binds Additional Authenticated Data (the field's tenant+table+column) into
14
14
  # the auth tag, so a ciphertext moved to a DIFFERENT column/table/tenant fails
15
15
  # the tag on read — it can't be transplanted. Old "v1:" rows (no AAD) keep
16
- # decrypting, so upgrading is a lazy re-encrypt (loam:encryption:rotate writes
16
+ # decrypting, so upgrading is a lazy re-encrypt (open_loam:encryption:rotate writes
17
17
  # v2), never a data migration. The AAD is authenticated but NOT secret — it
18
18
  # never conceals anything, it only pins WHERE the ciphertext is allowed to live.
19
19
  module Cipher
@@ -37,7 +37,7 @@ module Loam
37
37
  "#{version}:" + [iv + tag + ciphertext].pack("m0")
38
38
  end
39
39
 
40
- # Decrypt, or raise Loam::Encryption::DecryptionError. The wrong tenant's
40
+ # Decrypt, or raise OpenLoam::Encryption::DecryptionError. The wrong tenant's
41
41
  # key, a tampered blob, a truncated tag, a v2 blob read with the WRONG (or
42
42
  # missing) AAD, or plain garbage all fail the same loud way — never a
43
43
  # partial or silently-wrong plaintext. A v1 blob carries no AAD, so the
@@ -1,8 +1,8 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Encryption
3
3
  # The seam a real KMS plugs into. A provider turns (scope, purpose) into a
4
4
  # 32-byte data key; swap the default for a Vault/AWS-KMS-backed provider via
5
- # `Loam::Encryption.key_provider = MyKmsProvider.new` and NO call site
5
+ # `OpenLoam::Encryption.key_provider = MyKmsProvider.new` and NO call site
6
6
  # changes — Cipher and Encryptable only ever ask for a key.
7
7
  #
8
8
  # `scope` is a namespaced owner string: "tenant/5" for an entity field,
@@ -29,7 +29,7 @@ module Loam
29
29
  raise ArgumentError, "scope is required to derive a key" if scope.nil? || scope.to_s.empty?
30
30
 
31
31
  OpenSSL::KDF.hkdf(
32
- Loam::Encryption.master_key,
32
+ OpenLoam::Encryption.master_key,
33
33
  salt: SALT,
34
34
  # info binds the key to owner AND purpose. "tenant/5" here reproduces
35
35
  # the pre-scope format exactly, so existing ciphertext still decrypts.
@@ -1,22 +1,22 @@
1
1
  require "openssl"
2
- require "loam/encryption/key_provider"
3
- require "loam/encryption/cipher"
2
+ require "open_loam/encryption/key_provider"
3
+ require "open_loam/encryption/cipher"
4
4
 
5
- module Loam
5
+ module OpenLoam
6
6
  # Field-level encryption at rest, keyed per tenant.
7
7
  #
8
- # The facade the rest of Loam calls: `encrypt`/`decrypt` seal and open a value
8
+ # The facade the rest of OpenLoam calls: `encrypt`/`decrypt` seal and open a value
9
9
  # with the tenant's derived AES-256-GCM key, and `blind_index` computes the
10
10
  # per-tenant HMAC used to find an encrypted field by exact value. Key
11
11
  # derivation is delegated to a pluggable `key_provider` (HKDF by default, a
12
12
  # KMS in production), so this module holds the scheme, not the key material.
13
13
  module Encryption
14
- class Error < Loam::Error; end
14
+ class Error < OpenLoam::Error; end
15
15
 
16
16
  # Raised when a crypto operation is attempted with no master key configured.
17
17
  class MissingMasterKeyError < Error
18
- def initialize(msg = "Loam::Encryption has no master key. Set LOAM_MASTER_KEY (or " \
19
- "`Loam::Encryption.master_key = ...`) to a high-entropy secret, e.g. " \
18
+ def initialize(msg = "OpenLoam::Encryption has no master key. Set OPEN_LOAM_MASTER_KEY (or " \
19
+ "`OpenLoam::Encryption.master_key = ...`) to a high-entropy secret, e.g. " \
20
20
  "`SecureRandom.hex(32)`. NEVER commit it; use ENV or Rails credentials.")
21
21
  super
22
22
  end
@@ -42,18 +42,18 @@ module Loam
42
42
  end
43
43
 
44
44
  def master_key
45
- key = @master_key || ENV["LOAM_MASTER_KEY"]
45
+ key = @master_key || ENV["OPEN_LOAM_MASTER_KEY"]
46
46
  raise MissingMasterKeyError if key.nil? || key.empty?
47
47
  if key.bytesize < MASTER_KEY_MIN_BYTES
48
48
  raise MissingMasterKeyError,
49
- "LOAM_MASTER_KEY is too short (#{key.bytesize} bytes); use at least " \
49
+ "OPEN_LOAM_MASTER_KEY is too short (#{key.bytesize} bytes); use at least " \
50
50
  "#{MASTER_KEY_MIN_BYTES}, e.g. `SecureRandom.hex(32)`."
51
51
  end
52
52
  key
53
53
  end
54
54
 
55
55
  # Tenant-scoped operations — the default for entity fields via
56
- # Loam::Encryptable. nil stays nil (an unset field is not "the empty
56
+ # OpenLoam::Encryptable. nil stays nil (an unset field is not "the empty
57
57
  # string encrypted"); any other value is stringified and sealed.
58
58
  def encrypt(plaintext, tenant_id)
59
59
  encrypt_scoped(plaintext, tenant_scope(tenant_id))
@@ -89,7 +89,7 @@ module Loam
89
89
  # lives — the key scope (tenant/owner) + table + column. Reconstructed
90
90
  # identically on read and write, so a blob moved to a different column,
91
91
  # table, or tenant fails the auth tag. NOT the record id (see
92
- # Loam::Encryptable): the id is unknown at INSERT time, and binding it would
92
+ # OpenLoam::Encryptable): the id is unknown at INSERT time, and binding it would
93
93
  # force an ugly post-insert double-write; record-swap within one
94
94
  # tenant+table+column stays a documented residual.
95
95
  def aad(scope, table, column)
@@ -0,0 +1,57 @@
1
+ module OpenLoam
2
+ class Engine < ::Rails::Engine
3
+ # Not isolated on purpose: OpenLoam models live under the OpenLoam:: namespace but
4
+ # share the host app's routes/helpers, keeping the prototype surface small.
5
+
6
+ # Webhook dispatch listens to every OpenLoam event. Wired after initialization
7
+ # so the models it queries are loadable, and guarded against subscribing
8
+ # twice (see OpenLoam::Webhooks.subscribe!).
9
+ config.after_initialize do
10
+ OpenLoam::Webhooks.subscribe!
11
+ OpenLoam::DurableEvents.subscribe! # persist + retry durable subscribers (L-706)
12
+ OpenLoam::BusinessRules.subscribe!
13
+ OpenLoam::Widgets.register_builtins! # the default dashboard widgets
14
+ OpenLoam::Overrides.check! # warn about any stale disable/replace overrides
15
+
16
+ # The durability sweep runs per-tenant on a schedule (materialized into
17
+ # each tenant by OpenLoam::Scheduler.sync_tenant). interval:300 = every 5 min.
18
+ OpenLoam::Scheduler.register(
19
+ key: OpenLoam::DurableEvents::SWEEP_KEY, name: "Event redelivery sweep",
20
+ job_class: "OpenLoam::EventRedeliverySweepJob", schedule: "interval:300", scope: "tenant"
21
+ )
22
+ end
23
+
24
+ # OpenLoam's Active Record layer — TenantRecord, Auditable, Eventful and the
25
+ # rest — is defined inside ActiveSupport.on_load(:active_record) in
26
+ # lib/open_loam.rb, so those constants exist only once something has referenced
27
+ # ActiveRecord::Base.
28
+ #
29
+ # Zeitwerk eager-loads app/models alphabetically. A host model that inherits
30
+ # from OpenLoam::TenantRecord and sorts ahead of application_record.rb reaches
31
+ # the constant before anything has touched Base, and eager load dies with
32
+ # "uninitialized constant OpenLoam::TenantRecord". Whether an app hits it
33
+ # depends on its model names, so it shows up on a rename rather than on the
34
+ # change that caused it.
35
+ #
36
+ # Referencing Base here fires the hook while load order is still the
37
+ # engine's to decide instead of each host app's. :eager_load! is late enough
38
+ # that every active_record.* initializer has applied its configuration, and
39
+ # it runs in every environment (in development it is a no-op that still
40
+ # executes), so lazily-loaded apps are settled by the same line.
41
+ initializer "open_loam.active_record_layer", before: :eager_load! do
42
+ ActiveRecord::Base
43
+ end
44
+
45
+ # OpenLoam's own UI strings ship under lib/ (the gemspec packages lib/**/*), so
46
+ # register them on the app's I18n load path explicitly rather than relying on
47
+ # the default config/locales path (which the gem does not ship). An app
48
+ # overrides any key with its own config/locales/open_loam.<locale>.yml.
49
+ initializer "open_loam.i18n" do |app|
50
+ app.config.i18n.load_path += Dir[File.expand_path("locales/*.yml", __dir__)]
51
+ end
52
+
53
+ # lib/tasks/open_loam.rake (bin/rails open_loam:sync) is picked up by Rails::Engine's
54
+ # own lib/tasks loading. Loading it again from a `rake_tasks` block here
55
+ # would define the task twice and run its body twice.
56
+ end
57
+ end
@@ -1,14 +1,14 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Response enrichers: one module attaches a computed block onto ANOTHER
3
3
  # module's entity in admin/API responses, with no foreign-key coupling.
4
4
  #
5
5
  # # billing knows about equipment; equipment knows nothing about billing:
6
- # Loam::Enrichers.register("Equipment", key: "outstanding_balance") do |equipment|
6
+ # OpenLoam::Enrichers.register("Equipment", key: "outstanding_balance") do |equipment|
7
7
  # Invoice.where(equipment_id: equipment.id).sum(:balance)
8
8
  # end
9
9
  #
10
- # Loam::Enrichers.enrich(equipment) # => { "outstanding_balance" => 1200 }
11
- # Loam::Enrichers.enrich_many(equipments) # => { id => { key => value }, ... }
10
+ # OpenLoam::Enrichers.enrich(equipment) # => { "outstanding_balance" => 1200 }
11
+ # OpenLoam::Enrichers.enrich_many(equipments) # => { id => { key => value }, ... }
12
12
  #
13
13
  # Distinct from custom fields: a custom field is STORED on the record; an
14
14
  # enricher is COMPUTED at read time by different (possibly cross-module) code.
@@ -97,7 +97,7 @@ module Loam
97
97
 
98
98
  def warn_failure(enricher, error)
99
99
  Rails.logger&.warn(
100
- "[Loam::Enrichers] #{enricher.entity_type}/#{enricher.key} raised #{error.class}: #{error.message} — key omitted"
100
+ "[OpenLoam::Enrichers] #{enricher.entity_type}/#{enricher.key} raised #{error.class}: #{error.message} — key omitted"
101
101
  )
102
102
  end
103
103
  end
@@ -1,31 +1,31 @@
1
- module Loam
1
+ module OpenLoam
2
2
  class Error < StandardError; end
3
3
 
4
4
  # Raised whenever a tenant-scoped model is touched with no tenant in
5
- # Loam::Current. This is a structural guardrail, not a convention: forgetting
5
+ # OpenLoam::Current. This is a structural guardrail, not a convention: forgetting
6
6
  # the tenant context fails loudly (in tests, before it leaks) instead of
7
7
  # silently returning or writing cross-tenant data.
8
8
  class MissingTenantError < Error
9
- def initialize(msg = "No tenant set in Loam::Current — wrap this call in Loam.as_tenant(tenant) { ... }")
9
+ def initialize(msg = "No tenant set in OpenLoam::Current — wrap this call in OpenLoam.as_tenant(tenant) { ... }")
10
10
  super
11
11
  end
12
12
  end
13
13
 
14
14
  # Raised by admin controllers / callers when a policy check fails, and by a
15
- # Loam::Workflow transition the current actor's role may not perform.
15
+ # OpenLoam::Workflow transition the current actor's role may not perform.
16
16
  class NotAuthorizedError < Error; end
17
17
 
18
- # Raised when a Loam::Workflow transition is attempted from a state it does
18
+ # Raised when a OpenLoam::Workflow transition is attempted from a state it does
19
19
  # not move from ("approve a report that was never submitted"). Like every
20
- # Loam guardrail it fails at the call site rather than writing a state the
20
+ # OpenLoam guardrail it fails at the call site rather than writing a state the
21
21
  # machine says is impossible.
22
22
  class InvalidTransitionError < Error; end
23
23
 
24
24
  # Raised when an event name does not follow the `domain.thing.happened` convention.
25
25
  class InvalidEventNameError < Error; end
26
26
 
27
- # Raised by Loam::CustomFields#custom_field/#set_custom_field when the name
28
- # has no matching Loam::FieldDefinition for this tenant + entity. Fails
27
+ # Raised by OpenLoam::CustomFields#custom_field/#set_custom_field when the name
28
+ # has no matching OpenLoam::FieldDefinition for this tenant + entity. Fails
29
29
  # loudly at the access site rather than silently reading/writing nil.
30
30
  class UnknownCustomFieldError < Error; end
31
31
 
@@ -36,12 +36,12 @@ module Loam
36
36
  class FeatureDisabledError < Error; end
37
37
 
38
38
  # Raised when a filter/sort is attempted on a custom field the current role may
39
- # not read (Loam::CustomFieldIndex) — otherwise a filter would be an inference
39
+ # not read (OpenLoam::CustomFieldIndex) — otherwise a filter would be an inference
40
40
  # oracle on a restricted field. A NotAuthorizedError so admin controllers render
41
41
  # it as 403, like any other field-access denial.
42
42
  class FieldAccessError < NotAuthorizedError; end
43
43
 
44
- # Raised when a Loam::BusinessRule with a `block_transition` action vetoes a
44
+ # Raised when a OpenLoam::BusinessRule with a `block_transition` action vetoes a
45
45
  # workflow transition. Distinct from NotAuthorizedError (a role gate) and
46
46
  # InvalidTransitionError (an illegal move): the move is legal and permitted,
47
47
  # but a rule says "not under these conditions".
@@ -1,6 +1,6 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Scripted evaluation scoring for the golden tasks (L-303). An agent (or a
3
- # human) implements a task against a fresh Loam app; this scores the RESULT
3
+ # human) implements a task against a fresh OpenLoam app; this scores the RESULT
4
4
  # consistently — the test outcome plus the structural invariants — into a
5
5
  # machine-readable scorecard, so runs are comparable over time
6
6
  # (ai/benchmark_runs/). Driving the agent is out of scope; consistent scoring
@@ -1,14 +1,14 @@
1
- module Loam
2
- # The real-time bridge: pushes selected Loam events to a browser over
1
+ module OpenLoam
2
+ # The real-time bridge: pushes selected OpenLoam events to a browser over
3
3
  # Server-Sent Events, so the admin updates live instead of polling.
4
4
  #
5
5
  # SECURITY POSTURE — default OFF. An event reaches a browser only if ALL hold:
6
- # * its name matches a declared Loam.broadcast_events pattern (opt-in), AND
6
+ # * its name matches a declared OpenLoam.broadcast_events pattern (opt-in), AND
7
7
  # * its tenant matches the connected tenant (isolation), AND
8
8
  # * its audience includes the connected actor (a payload `user_id`, if any,
9
9
  # is the sole recipient; no `user_id` means tenant-wide).
10
10
  #
11
- # FAN-OUT is behind a swappable broadcaster seam (Loam::EventStream.broadcaster).
11
+ # FAN-OUT is behind a swappable broadcaster seam (OpenLoam::EventStream.broadcaster).
12
12
  # The default in-process broadcaster only sees events published in THIS process
13
13
  # — fine for the single-process prototype; a multi-process deploy swaps in a
14
14
  # Redis/SolidCable-backed broadcaster with no controller change (see
@@ -24,10 +24,10 @@ module Loam
24
24
  # Is this event name allowed to reach browsers at all? Empty allow-list →
25
25
  # false, always (nothing leaks by default).
26
26
  def broadcastable?(event_name)
27
- Loam.broadcast_events.any? do |pattern|
28
- next false if Loam::Overrides.disabled?(:broadcast_events, pattern) # an app can turn a default pattern off
27
+ OpenLoam.broadcast_events.any? do |pattern|
28
+ next false if OpenLoam::Overrides.disabled?(:broadcast_events, pattern) # an app can turn a default pattern off
29
29
 
30
- Loam::Events.pattern_matches?(pattern, event_name)
30
+ OpenLoam::Events.pattern_matches?(pattern, event_name)
31
31
  end
32
32
  end
33
33
 
@@ -42,9 +42,9 @@ module Loam
42
42
  recipient.nil? || recipient == actor&.id
43
43
  end
44
44
 
45
- # One SSE message: an `event:` line (the Loam event name) and a `data:`
45
+ # One SSE message: an `event:` line (the OpenLoam event name) and a `data:`
46
46
  # line (JSON), ended by a blank line. Only small id-ish keys ride along —
47
- # Loam events carry no attribute values, and this slices to a safe set as
47
+ # OpenLoam events carry no attribute values, and this slices to a safe set as
48
48
  # belt-and-suspenders (tenant_id is dropped; it is implied by the connection).
49
49
  def frame(event_name, payload)
50
50
  "event: #{event_name}\ndata: #{safe_payload(payload).to_json}\n\n"
@@ -54,21 +54,21 @@ module Loam
54
54
 
55
55
  def safe_payload(payload)
56
56
  # id-ish keys only — never attribute values. percent/status ride along for
57
- # the progress bar (Loam::ProgressJob); both are non-sensitive.
57
+ # the progress bar (OpenLoam::ProgressJob); both are non-sensitive.
58
58
  payload.symbolize_keys.slice(:id, :type, :user_id, :from, :to, :percent, :status).compact
59
59
  end
60
60
  end
61
61
 
62
- # The default fan-out: subscribe to Loam::Events in THIS process and forward
62
+ # The default fan-out: subscribe to OpenLoam::Events in THIS process and forward
63
63
  # the events deliverable to (tenant, actor) to a sink. A `sink` is anything
64
64
  # answering #call(sse_string) — the controller's is a Queue push.
65
65
  class InProcessBroadcaster
66
66
  # Returns an opaque handle to pass back to #unsubscribe.
67
67
  def subscribe(tenant:, actor:, &sink)
68
- Loam::Events.subscribe_all do |event_name, payload|
69
- next unless Loam::EventStream.deliverable?(event_name, payload, tenant: tenant, actor: actor)
68
+ OpenLoam::Events.subscribe_all do |event_name, payload|
69
+ next unless OpenLoam::EventStream.deliverable?(event_name, payload, tenant: tenant, actor: actor)
70
70
 
71
- sink.call(Loam::EventStream.frame(event_name, payload))
71
+ sink.call(OpenLoam::EventStream.frame(event_name, payload))
72
72
  end
73
73
  end
74
74
 
@@ -1,18 +1,18 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Lifecycle events by convention. Included in every generated entity:
3
3
  #
4
- # class Equipment < Loam::TenantRecord
5
- # include Loam::Eventful
4
+ # class Equipment < OpenLoam::TenantRecord
5
+ # include OpenLoam::Eventful
6
6
  # event_domain :rental # -> "rental.equipment.created" etc.
7
7
  # end
8
8
  #
9
- # Custom business events are published explicitly with Loam::Events.publish.
9
+ # Custom business events are published explicitly with OpenLoam::Events.publish.
10
10
  module Eventful
11
11
  extend ActiveSupport::Concern
12
12
 
13
13
  included do
14
- class_attribute :loam_event_domain, default: "app"
15
- class_attribute :loam_event_entity, default: nil
14
+ class_attribute :open_loam_event_domain, default: "app"
15
+ class_attribute :open_loam_event_entity, default: nil
16
16
 
17
17
  after_create_commit { publish_lifecycle_event("created") }
18
18
  after_update_commit { publish_lifecycle_event("updated") }
@@ -21,23 +21,23 @@ module Loam
21
21
 
22
22
  class_methods do
23
23
  def event_domain(domain)
24
- self.loam_event_domain = domain.to_s
24
+ self.open_loam_event_domain = domain.to_s
25
25
  end
26
26
 
27
27
  # The middle segment of the event name. It defaults to the model's param
28
28
  # key, which is right for app models but awkward for namespaced ones —
29
- # Loam::Comment would publish "loam.loam_comment.created" rather than
30
- # "loam.comment.created".
29
+ # OpenLoam::Comment would publish "open_loam.open_loam_comment.created" rather than
30
+ # "open_loam.comment.created".
31
31
  def event_entity(name)
32
- self.loam_event_entity = name.to_s
32
+ self.open_loam_event_entity = name.to_s
33
33
  end
34
34
  end
35
35
 
36
36
  private
37
37
 
38
38
  def publish_lifecycle_event(happened)
39
- Loam::Events.publish(
40
- "#{loam_event_domain}.#{loam_event_entity || model_name.param_key}.#{happened}",
39
+ OpenLoam::Events.publish(
40
+ "#{open_loam_event_domain}.#{open_loam_event_entity || model_name.param_key}.#{happened}",
41
41
  { id: id, type: self.class.name }
42
42
  )
43
43
  end