open-loam 0.1.1 → 0.3.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 (287) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +148 -8
  3. data/LICENSE +1 -1
  4. data/README.md +27 -25
  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/open_loam/event_log_prune_job.rb +15 -0
  8. data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
  9. data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +17 -8
  10. data/app/models/open_loam/api_token.rb +63 -0
  11. data/app/models/{loam → open_loam}/audit_record.rb +3 -3
  12. data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
  13. data/app/models/{loam → open_loam}/business_rule.rb +10 -10
  14. data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
  15. data/app/models/{loam → open_loam}/comment.rb +9 -9
  16. data/app/models/{loam → open_loam}/config.rb +7 -7
  17. data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
  18. data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
  19. data/app/models/open_loam/dictionary.rb +20 -0
  20. data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
  21. data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
  22. data/app/models/open_loam/event_record.rb +42 -0
  23. data/app/models/{loam → open_loam}/field_definition.rb +8 -8
  24. data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
  25. data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
  26. data/app/models/{loam → open_loam}/membership.rb +6 -6
  27. data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
  28. data/app/models/{loam → open_loam}/notification.rb +10 -10
  29. data/app/models/{loam → open_loam}/pending_action.rb +70 -26
  30. data/app/models/{loam → open_loam}/perspective.rb +5 -5
  31. data/app/models/{loam → open_loam}/progress_job.rb +7 -7
  32. data/app/models/{loam → open_loam}/record_lock.rb +5 -5
  33. data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
  34. data/app/models/open_loam/search_token.rb +9 -0
  35. data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
  36. data/app/models/open_loam/sso_provider.rb +66 -0
  37. data/app/models/open_loam/tenant.rb +20 -0
  38. data/app/models/{loam → open_loam}/translation.rb +5 -5
  39. data/app/models/open_loam/webhook_endpoint.rb +39 -0
  40. data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
  41. data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
  42. data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +6 -2
  43. data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
  44. data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +2 -1
  45. data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +32 -22
  46. data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
  47. data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
  48. data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
  49. data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
  50. data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +8 -5
  51. data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
  52. data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
  53. data/lib/generators/open_loam/entity/templates/views/index.html.erb +94 -0
  54. data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
  55. data/lib/generators/open_loam/entity/templates/views/show.html.erb +38 -0
  56. data/lib/generators/{loam → open_loam}/install/install_generator.rb +58 -47
  57. data/lib/generators/open_loam/install/templates/AGENTS.md +345 -0
  58. data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
  59. data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
  60. data/lib/generators/open_loam/install/templates/admin/api_tokens_controller.rb +48 -0
  61. data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +23 -0
  62. data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +101 -38
  63. data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
  64. data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
  65. data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
  66. data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
  67. data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +12 -0
  68. data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
  69. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
  70. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
  71. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
  72. data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
  73. data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
  74. data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +7 -5
  75. data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
  76. data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
  77. data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
  78. data/lib/generators/open_loam/install/templates/admin/history_controller.rb +64 -0
  79. data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +2 -2
  80. data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +25 -13
  81. data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +7 -7
  82. data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -4
  83. data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
  84. data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +11 -6
  85. data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +5 -3
  86. data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
  87. data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
  88. data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
  89. data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +13 -7
  90. data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
  91. data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +23 -0
  92. data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
  93. data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
  94. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
  95. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
  96. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
  97. data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +7 -5
  98. data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +33 -34
  99. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
  100. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
  101. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +8 -1
  102. data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +7 -5
  103. data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
  104. data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
  105. data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
  106. data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
  107. data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
  108. data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
  109. data/lib/generators/open_loam/install/templates/api_base_controller.rb +127 -0
  110. data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
  111. data/lib/generators/open_loam/install/templates/import_job.rb +38 -0
  112. data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
  113. data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +87 -75
  114. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
  115. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
  116. 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
  117. 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
  118. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
  119. data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
  120. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
  121. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
  122. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
  123. 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
  124. 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
  125. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_event_records.rb +13 -0
  126. 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
  127. data/lib/generators/{loam/install/templates/migrations/create_loam_inbound_webhooks.rb → open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb} +9 -10
  128. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
  129. 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
  130. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
  131. 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
  132. data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
  133. 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
  134. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
  135. 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
  136. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
  137. data/lib/generators/{loam/install/templates/migrations/create_loam_sso_providers.rb → open_loam/install/templates/migrations/create_open_loam_sso_providers.rb} +15 -12
  138. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
  139. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
  140. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
  141. data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
  142. data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
  143. data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
  144. data/lib/open-loam.rb +2 -2
  145. data/lib/{loam → open_loam}/attachable.rb +4 -4
  146. data/lib/{loam → open_loam}/auditable.rb +23 -23
  147. data/lib/{loam → open_loam}/auth_throttle.rb +20 -12
  148. data/lib/{loam → open_loam}/base32.rb +1 -1
  149. data/lib/{loam → open_loam}/bulk.rb +7 -7
  150. data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
  151. data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
  152. data/lib/{loam → open_loam}/business_rules.rb +19 -19
  153. data/lib/{loam → open_loam}/commentable.rb +6 -6
  154. data/lib/{loam → open_loam}/configs.rb +19 -19
  155. data/lib/{loam → open_loam}/cron.rb +2 -2
  156. data/lib/{loam → open_loam}/csv.rb +2 -2
  157. data/lib/{loam → open_loam}/current.rb +5 -5
  158. data/lib/{loam → open_loam}/custom_field_index.rb +33 -30
  159. data/lib/{loam → open_loam}/custom_fields.rb +17 -17
  160. data/lib/{loam → open_loam}/dashboard.rb +5 -5
  161. data/lib/{loam → open_loam}/dictionaries.rb +12 -12
  162. data/lib/{loam → open_loam}/durable_events.rb +19 -19
  163. data/lib/{loam → open_loam}/encryptable.rb +39 -34
  164. data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
  165. data/lib/{loam → open_loam}/encryption/key_provider.rb +19 -3
  166. data/lib/{loam → open_loam}/encryption.rb +58 -15
  167. data/lib/open_loam/engine.rb +65 -0
  168. data/lib/{loam → open_loam}/enrichers.rb +5 -5
  169. data/lib/{loam → open_loam}/errors.rb +20 -10
  170. data/lib/{loam → open_loam}/eval.rb +2 -2
  171. data/lib/open_loam/event_log.rb +77 -0
  172. data/lib/{loam → open_loam}/event_stream.rb +14 -14
  173. data/lib/{loam → open_loam}/eventful.rb +12 -12
  174. data/lib/{loam → open_loam}/events.rb +5 -5
  175. data/lib/{loam → open_loam}/export.rb +12 -12
  176. data/lib/{loam → open_loam}/features.rb +15 -15
  177. data/lib/{loam → open_loam}/generated_key.rb +6 -6
  178. data/lib/{loam → open_loam}/import.rb +12 -15
  179. data/lib/{loam → open_loam}/inbound_webhooks.rb +19 -18
  180. data/lib/{loam → open_loam}/lifecycle.rb +54 -25
  181. data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
  182. data/lib/{loam → open_loam}/mcp/server.rb +9 -9
  183. data/lib/{loam → open_loam}/mcp.rb +31 -22
  184. data/lib/{loam → open_loam}/notifications.rb +5 -5
  185. data/lib/{loam → open_loam}/open_api.rb +16 -16
  186. data/lib/open_loam/outbound_url.rb +100 -0
  187. data/lib/{loam → open_loam}/overrides.rb +10 -10
  188. data/lib/{loam → open_loam}/pending_actions.rb +9 -9
  189. data/lib/{loam → open_loam}/permissions.rb +8 -8
  190. data/lib/{loam → open_loam}/perspectives.rb +9 -9
  191. data/lib/{loam → open_loam}/policy.rb +21 -8
  192. data/lib/{loam → open_loam}/progress.rb +6 -6
  193. data/lib/{loam → open_loam}/record_locks.rb +9 -9
  194. data/lib/{loam → open_loam}/scheduler.rb +20 -20
  195. data/lib/{loam → open_loam}/search/driver.rb +3 -3
  196. data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
  197. data/lib/{loam → open_loam}/search/token_driver.rb +16 -16
  198. data/lib/{loam → open_loam}/search.rb +5 -5
  199. data/lib/{loam → open_loam}/searchable.rb +22 -22
  200. data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
  201. data/lib/{loam → open_loam}/sso/claims.rb +1 -1
  202. data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
  203. data/lib/open_loam/sso/http_client.rb +51 -0
  204. data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
  205. data/lib/{loam → open_loam}/sso.rb +39 -18
  206. data/lib/{loam → open_loam}/telemetry.rb +6 -6
  207. data/lib/{loam → open_loam}/tenant_record.rb +10 -10
  208. data/lib/{loam → open_loam}/test_helpers.rb +4 -4
  209. data/lib/{loam → open_loam}/totp.rb +3 -3
  210. data/lib/{loam → open_loam}/translatable.rb +24 -24
  211. data/lib/{loam → open_loam}/undo.rb +11 -11
  212. data/lib/open_loam/version.rb +3 -0
  213. data/lib/{loam → open_loam}/webhooks.rb +4 -4
  214. data/lib/{loam → open_loam}/widgets.rb +9 -9
  215. data/lib/{loam → open_loam}/workflow.rb +45 -45
  216. data/lib/open_loam.rb +124 -0
  217. data/lib/tasks/open_loam.rake +203 -0
  218. data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
  219. data/lib/tasks/open_loam_mcp.rake +20 -0
  220. metadata +247 -242
  221. data/app/models/loam/api_token.rb +0 -40
  222. data/app/models/loam/dictionary.rb +0 -20
  223. data/app/models/loam/search_token.rb +0 -9
  224. data/app/models/loam/sso_provider.rb +0 -35
  225. data/app/models/loam/tenant.rb +0 -20
  226. data/app/models/loam/webhook_endpoint.rb +0 -26
  227. data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
  228. data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
  229. data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
  230. data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
  231. data/lib/generators/loam/install/templates/AGENTS.md +0 -341
  232. data/lib/generators/loam/install/templates/admin/api_tokens_controller.rb +0 -30
  233. data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
  234. data/lib/generators/loam/install/templates/admin/history_controller.rb +0 -38
  235. data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
  236. data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
  237. data/lib/generators/loam/install/templates/api_base_controller.rb +0 -90
  238. data/lib/generators/loam/install/templates/import_job.rb +0 -25
  239. data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
  240. data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
  241. data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
  242. data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
  243. data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
  244. data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
  245. data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
  246. data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
  247. data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
  248. data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
  249. data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
  250. data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
  251. data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
  252. data/lib/loam/engine.rb +0 -57
  253. data/lib/loam/sso/http_client.rb +0 -42
  254. data/lib/loam/version.rb +0 -3
  255. data/lib/loam.rb +0 -122
  256. data/lib/tasks/loam.rake +0 -171
  257. data/lib/tasks/loam_mcp.rake +0 -20
  258. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
  259. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
  260. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
  261. /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
  262. /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
  263. /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
  264. /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
  265. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
  266. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
  267. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
  268. /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
  269. /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
  270. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
  271. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
  272. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
  273. /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
  274. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
  275. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
  276. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
  277. /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
  278. /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
  279. /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
  280. /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
  281. /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
  282. /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
  283. /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
  284. /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
  285. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
  286. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
  287. /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
@@ -1,19 +1,19 @@
1
- module Loam
2
- # Staging and reviewing gated mutations (see Loam::PendingAction).
1
+ module OpenLoam
2
+ # Staging and reviewing gated mutations (see OpenLoam::PendingAction).
3
3
  #
4
- # pending = Loam::PendingActions.stage(
4
+ # pending = OpenLoam::PendingActions.stage(
5
5
  # summary: "Raise the excavator's daily rate to 1100",
6
6
  # on: equipment, action: :update, changes: { daily_rate: 1100 }
7
7
  # )
8
8
  # pending.preview # => { "daily_rate" => { "from" => 950, "to" => 1100 } }
9
9
  # pending.approve!(by: manager) # role-gated; executes the change
10
10
  #
11
- # `stage` NEVER touches the target — it only records the intent. Loam does not
11
+ # `stage` NEVER touches the target — it only records the intent. OpenLoam does not
12
12
  # intercept Active Record globally (that would be fragile and out of scope);
13
13
  # this is the primitive a confirm-mode write path calls instead of saving.
14
14
  module PendingActions
15
15
  class << self
16
- def stage(summary:, on:, action:, changes: {}, idempotency_key: nil, actor: Loam::Current.actor)
16
+ def stage(summary:, on:, action:, changes: {}, idempotency_key: nil, actor: OpenLoam::Current.actor)
17
17
  target_type, target_id = resolve_target(on)
18
18
  changes = changes.transform_keys(&:to_s)
19
19
  key = idempotency_key || compute_key(target_type, target_id, action, changes)
@@ -21,10 +21,10 @@ module Loam
21
21
  # The same proposal staged twice collapses to one row — but only a still
22
22
  # PENDING one. A rejected or executed proposal with this key may be
23
23
  # re-staged as a fresh pending row (the partial index allows the coexist).
24
- existing = Loam::PendingAction.pending.find_by(idempotency_key: key)
24
+ existing = OpenLoam::PendingAction.pending.find_by(idempotency_key: key)
25
25
  return existing if existing
26
26
 
27
- Loam::PendingAction.create!(
27
+ OpenLoam::PendingAction.create!(
28
28
  actor_id: actor&.id,
29
29
  action_type: action.to_s,
30
30
  target_type: target_type,
@@ -35,7 +35,7 @@ module Loam
35
35
  )
36
36
  rescue ActiveRecord::RecordNotUnique
37
37
  # Lost a concurrent double-stage race — return the pending row that won.
38
- Loam::PendingAction.pending.find_by!(idempotency_key: key)
38
+ OpenLoam::PendingAction.pending.find_by!(idempotency_key: key)
39
39
  end
40
40
 
41
41
  private
@@ -53,7 +53,7 @@ module Loam
53
53
  # the same key; a reordered hash is treated as a different proposal.
54
54
  def compute_key(target_type, target_id, action, changes)
55
55
  payload = [ target_type, target_id, action, changes.to_json ].join("|")
56
- Loam::Encryption.blind_index_scoped(payload, "tenant/#{Loam.tenant!.id}")
56
+ OpenLoam::Encryption.blind_index_scoped(payload, "tenant/#{OpenLoam.tenant!.id}")
57
57
  end
58
58
  end
59
59
  end
@@ -1,21 +1,21 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Feature-string permissions with wildcards (L-705) — a fine-grained capability
3
3
  # layer that sits UNDER the coarse role. Roles answer "manager or clerk";
4
4
  # permissions answer "may this role do `equipment.edit`" without inventing a new
5
- # role for every distinction. Orthogonal to Loam::Policy (which gates field-level
6
- # writes on a record) and to Loam::Features (a per-tenant capability switch).
5
+ # role for every distinction. Orthogonal to OpenLoam::Policy (which gates field-level
6
+ # writes on a record) and to OpenLoam::Features (a per-tenant capability switch).
7
7
  #
8
8
  # Declared once, in the initializer (like broadcast_events / scheduler defaults):
9
9
  #
10
- # Loam::Permissions.configure do
10
+ # OpenLoam::Permissions.configure do
11
11
  # role :admin, allow: "*" # everything
12
12
  # role :manager, allow: %w[equipment.* damage_report.* billing.read]
13
13
  # role :clerk, allow: %w[equipment.read damage_report.create]
14
14
  # end
15
15
  #
16
- # Loam::Permissions.allow?(:clerk, "equipment.read") # => true
17
- # Loam::Permissions.allow?(:clerk, "equipment.edit") # => false
18
- # Loam.can?("equipment.edit") # for the current actor's role
16
+ # OpenLoam::Permissions.allow?(:clerk, "equipment.read") # => true
17
+ # OpenLoam::Permissions.allow?(:clerk, "equipment.edit") # => false
18
+ # OpenLoam.can?("equipment.edit") # for the current actor's role
19
19
  #
20
20
  # DENY BY DEFAULT: a role with no matching grant (or no grants at all) is denied.
21
21
  #
@@ -72,7 +72,7 @@ module Loam
72
72
  # Tiny DSL so `configure { role ... }` reads well without a receiver.
73
73
  class DSL
74
74
  def role(name, allow:)
75
- Loam::Permissions.role(name, allow: allow)
75
+ OpenLoam::Permissions.role(name, allow: allow)
76
76
  end
77
77
  end
78
78
  end
@@ -1,20 +1,20 @@
1
- module Loam
2
- # Reading the saved views (Loam::Perspective) a user may see for an entity.
1
+ module OpenLoam
2
+ # Reading the saved views (OpenLoam::Perspective) a user may see for an entity.
3
3
  #
4
- # Loam::Perspectives.visible_to("Equipment", user: current_actor) # pick list
5
- # Loam::Perspectives.default_for("Equipment", user: current_actor) # the applicable default
6
- # Loam::Perspectives.resolve("Equipment", user:, id: params[:perspective_id])
4
+ # OpenLoam::Perspectives.visible_to("Equipment", user: current_actor) # pick list
5
+ # OpenLoam::Perspectives.default_for("Equipment", user: current_actor) # the applicable default
6
+ # OpenLoam::Perspectives.resolve("Equipment", user:, id: params[:perspective_id])
7
7
  #
8
- # Every query is tenant-scoped by Loam::Perspective. The membership role used
8
+ # Every query is tenant-scoped by OpenLoam::Perspective. The membership role used
9
9
  # for role-shared views is the user's role in the CURRENT tenant.
10
10
  module Perspectives
11
11
  class << self
12
12
  # The views this user may see for an entity: their own private ones, the
13
13
  # role-shared ones matching their membership role, and the tenant-wide
14
14
  # ones. Default(s) first, then by name. Named `visible_to` rather than
15
- # `for` so it does not read like Loam::Policy.for (which builds a policy).
15
+ # `for` so it does not read like OpenLoam::Policy.for (which builds a policy).
16
16
  def visible_to(entity_type, user:)
17
- base = Loam::Perspective.where(entity_type: entity_type.to_s)
17
+ base = OpenLoam::Perspective.where(entity_type: entity_type.to_s)
18
18
  role = membership_role(user)
19
19
 
20
20
  # Build the OR chain on UNORDERED relations — `.or` refuses to combine
@@ -55,7 +55,7 @@ module Loam
55
55
  def membership_role(user)
56
56
  return nil unless user
57
57
 
58
- Loam::Membership.find_by(user_id: user.id)&.role
58
+ OpenLoam::Membership.find_by(user_id: user.id)&.role
59
59
  end
60
60
  end
61
61
  end
@@ -1,13 +1,13 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Base policy. One policy class per entity, one instance per (actor, record)
3
3
  # pair. Action checks (read?/create?/update?/destroy?) default to "any member
4
4
  # of the current tenant"; field-level write access is declared, not coded:
5
5
  #
6
- # class EquipmentPolicy < Loam::Policy
6
+ # class EquipmentPolicy < OpenLoam::Policy
7
7
  # field :daily_rate, writable: [:manager]
8
8
  # end
9
9
  #
10
- # Roles come from Loam::Membership (actor + current tenant -> role).
10
+ # Roles come from OpenLoam::Membership (actor + current tenant -> role).
11
11
  class Policy
12
12
  class << self
13
13
  def field_rules
@@ -19,10 +19,23 @@ module Loam
19
19
  end
20
20
 
21
21
  def for(record)
22
- policy_class = "#{record.class.name}Policy".safe_constantize
23
- raise Error, "No policy defined for #{record.class.name} (expected #{record.class.name}Policy)" unless policy_class
22
+ policy_class_for(record.class).new(OpenLoam::Current.actor, record)
23
+ end
24
+
25
+ # A policy for a MODEL rather than one record — for bulk paths (export,
26
+ # import) that decide the column set once, before any row is read. The
27
+ # blank instance is a stand-in: the field checks key off the role, but
28
+ # the custom-field ones need record.class to find the definitions.
29
+ def for_model(model, actor)
30
+ policy_class_for(model).new(actor, model.new)
31
+ end
24
32
 
25
- policy_class.new(Loam::Current.actor, record)
33
+ # Falling back to the base Policy for a model with no policy class would
34
+ # fail OPEN — every check there answers "any member", so a missing policy
35
+ # would silently grant what an explicit one restricts.
36
+ def policy_class_for(model)
37
+ "#{model.name}Policy".safe_constantize ||
38
+ raise(Error, "No policy defined for #{model.name} (expected #{model.name}Policy)")
26
39
  end
27
40
  end
28
41
 
@@ -36,7 +49,7 @@ module Loam
36
49
  def role
37
50
  return nil unless actor
38
51
 
39
- @role ||= Loam::Membership.find_by(user_id: actor.id)&.role&.to_sym
52
+ @role ||= OpenLoam::Membership.find_by(user_id: actor.id)&.role&.to_sym
40
53
  end
41
54
 
42
55
  def member? = role.present?
@@ -73,7 +86,7 @@ module Loam
73
86
  end
74
87
 
75
88
  # Same semantics as the static `field writable:` declaration, but for a
76
- # runtime Loam::FieldDefinition (see Loam::CustomFields): no
89
+ # runtime OpenLoam::FieldDefinition (see OpenLoam::CustomFields): no
77
90
  # writable_roles recorded means any member may write it.
78
91
  def custom_field_writable?(field_name)
79
92
  return false unless member?
@@ -1,8 +1,8 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Entry point for progress tracking. Start a job, then drive it through the
3
- # returned Loam::ProgressJob:
3
+ # returned OpenLoam::ProgressJob:
4
4
  #
5
- # progress = Loam::Progress.start(name: "Reindex", total: equipment.count)
5
+ # progress = OpenLoam::Progress.start(name: "Reindex", total: equipment.count)
6
6
  # equipment.find_each do |record|
7
7
  # break if progress.cancelled? # cooperative cancel
8
8
  # do_work(record)
@@ -12,18 +12,18 @@ module Loam
12
12
  #
13
13
  # The job is created in the current tenant (and stamped with the current
14
14
  # actor), so it is only visible and streamable there. In a background job,
15
- # establish the tenant with Loam.as_tenant first, like the webhook/digest jobs.
15
+ # establish the tenant with OpenLoam.as_tenant first, like the webhook/digest jobs.
16
16
  module Progress
17
17
  module_function
18
18
 
19
19
  def start(name:, total:, key: nil)
20
- Loam::ProgressJob.create!(
20
+ OpenLoam::ProgressJob.create!(
21
21
  key: key,
22
22
  name: name,
23
23
  total: total.to_i,
24
24
  completed: 0,
25
25
  status: "running",
26
- actor_id: Loam.actor&.id,
26
+ actor_id: OpenLoam.actor&.id,
27
27
  started_at: Time.current
28
28
  )
29
29
  end
@@ -1,15 +1,15 @@
1
- module Loam
2
- # Advisory record locks (Loam::RecordLock) — a "who's editing this" courtesy
1
+ module OpenLoam
2
+ # Advisory record locks (OpenLoam::RecordLock) — a "who's editing this" courtesy
3
3
  # for the multi-user admin.
4
4
  #
5
- # Loam::RecordLocks.acquire(record, by: current_actor) # take/refresh, or nil if held
6
- # Loam::RecordLocks.holder(record) # the user holding it, or nil
7
- # Loam::RecordLocks.release(record, by: current_actor) # give it up
8
- # Loam::RecordLocks.force_release(record) # manager override
5
+ # OpenLoam::RecordLocks.acquire(record, by: current_actor) # take/refresh, or nil if held
6
+ # OpenLoam::RecordLocks.holder(record) # the user holding it, or nil
7
+ # OpenLoam::RecordLocks.release(record, by: current_actor) # give it up
8
+ # OpenLoam::RecordLocks.force_release(record) # manager override
9
9
  #
10
10
  # THIS IS ADVISORY: a held lock warns, it does not hard-block — optimistic
11
11
  # locking (lock_version) is the actual guarantee that two edits can't clobber.
12
- # Every query is tenant-scoped by Loam::RecordLock, so a lock is invisible and
12
+ # Every query is tenant-scoped by OpenLoam::RecordLock, so a lock is invisible and
13
13
  # unaffectable from another tenant.
14
14
  module RecordLocks
15
15
  DEFAULT_TTL = 5.minutes
@@ -25,7 +25,7 @@ module Loam
25
25
  lock = lock_row(record)
26
26
  return nil if lock && !lock.expired? && lock.locked_by_id != by.id
27
27
 
28
- lock ||= Loam::RecordLock.new(lockable_type: type_of(record), lockable_id: record.id)
28
+ lock ||= OpenLoam::RecordLock.new(lockable_type: type_of(record), lockable_id: record.id)
29
29
  # A fresh session token when the lock is newly created or taken over from
30
30
  # an expired holder; kept as-is on a heartbeat by the same user.
31
31
  lock.token = SecureRandom.hex(16) if lock.new_record? || lock.locked_by_id != by.id
@@ -71,7 +71,7 @@ module Loam
71
71
  private
72
72
 
73
73
  def lock_row(record)
74
- Loam::RecordLock.find_by(lockable_type: type_of(record), lockable_id: record.id)
74
+ OpenLoam::RecordLock.find_by(lockable_type: type_of(record), lockable_id: record.id)
75
75
  end
76
76
 
77
77
  def type_of(record)
@@ -1,7 +1,7 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Per-tenant recurring jobs. Modules self-register default schedules; an app
3
- # also creates them from the admin. A runner calls Loam::Scheduler.tick
4
- # periodically (wire `loam:scheduler:tick` to system cron — every minute) and
3
+ # also creates them from the admin. A runner calls OpenLoam::Scheduler.tick
4
+ # periodically (wire `open_loam:scheduler:tick` to system cron — every minute) and
5
5
  # each DUE schedule enqueues its ActiveJob.
6
6
  #
7
7
  # NO DOUBLE-FIRE — the whole correctness story. tick ATOMICALLY CLAIMS due
@@ -12,7 +12,7 @@ module Loam
12
12
  # The mechanism is behind `claim_due`, chosen per adapter. A claim also stamps
13
13
  # `locked_until` so a crashed worker's rows free themselves after LOCK_TTL.
14
14
  module Scheduler
15
- class UnknownJobError < Loam::Error; end
15
+ class UnknownJobError < OpenLoam::Error; end
16
16
 
17
17
  class << self
18
18
  # --- declarative registry (like broadcast_events / feature_defaults) ---
@@ -31,14 +31,14 @@ module Loam
31
31
  end
32
32
 
33
33
  # Materialize the registered TENANT-scope schedules as rows in `tenant`
34
- # (idempotent — safe from on_tenant_created and `loam:sync`). System-scope
34
+ # (idempotent — safe from on_tenant_created and `open_loam:sync`). System-scope
35
35
  # schedules are created explicitly by the app (they are not per-tenant).
36
36
  def sync_tenant(tenant)
37
- Loam.as_tenant(tenant) do
37
+ OpenLoam.as_tenant(tenant) do
38
38
  registered.each do |default|
39
39
  next unless default[:scope] == "tenant"
40
40
 
41
- job = Loam::ScheduledJob.find_or_initialize_by(key: default[:key])
41
+ job = OpenLoam::ScheduledJob.find_or_initialize_by(key: default[:key])
42
42
  job.name = default[:name]
43
43
  job.job_class = default[:job_class]
44
44
  job.schedule = default[:schedule]
@@ -56,7 +56,7 @@ module Loam
56
56
  # failure never blocks the others (its lock is released so a later tick
57
57
  # retries it).
58
58
  def tick(now: Time.current)
59
- Loam::Telemetry.span("scheduler_tick") { run_tick(now) }
59
+ OpenLoam::Telemetry.span("scheduler_tick") { run_tick(now) }
60
60
  end
61
61
 
62
62
  def run_tick(now)
@@ -69,7 +69,7 @@ module Loam
69
69
  fired += 1
70
70
  rescue StandardError => error
71
71
  job.update_columns(locked_until: nil, updated_at: now)
72
- logger&.error("[loam scheduler] #{job.key} failed: #{error.class}: #{error.message}")
72
+ logger&.error("[open_loam scheduler] #{job.key} failed: #{error.class}: #{error.message}")
73
73
  end
74
74
  end
75
75
  fired
@@ -83,7 +83,7 @@ module Loam
83
83
 
84
84
  # THE code-execution guard: a job_class must resolve to a real ActiveJob
85
85
  # subclass AND be ALLOWLISTED — either registered here or listed in
86
- # Loam.schedulable_jobs. "Any ActiveJob" would let a tenant admin schedule
86
+ # OpenLoam.schedulable_jobs. "Any ActiveJob" would let a tenant admin schedule
87
87
  # ActiveStorage::PurgeJob, a mailer's delivery job, etc.
88
88
  def resolve_job_class(name)
89
89
  name = name.to_s
@@ -94,7 +94,7 @@ module Loam
94
94
  end
95
95
 
96
96
  def allowed_job_class?(name)
97
- registered.any? { |default| default[:job_class] == name } || Loam.schedulable_jobs.include?(name)
97
+ registered.any? { |default| default[:job_class] == name } || OpenLoam.schedulable_jobs.include?(name)
98
98
  end
99
99
 
100
100
  def resolve_job_class!(name)
@@ -113,36 +113,36 @@ module Loam
113
113
  if job.scope == "system"
114
114
  klass.perform_later
115
115
  else
116
- # Carry the tenant explicitly (ActiveJob doesn't serialize Loam::Current),
116
+ # Carry the tenant explicitly (ActiveJob doesn't serialize OpenLoam::Current),
117
117
  # and enqueue inside the tenant so any enqueue-time scoping is correct.
118
- Loam.as_tenant(job.tenant) { klass.perform_later(tenant_id: job.tenant_id) }
118
+ OpenLoam.as_tenant(job.tenant) { klass.perform_later(tenant_id: job.tenant_id) }
119
119
  end
120
120
  end
121
121
 
122
122
  # Atomically claim due, unlocked schedules across ALL tenants (a blessed
123
123
  # cross-tenant scan — the runner has no tenant context, like
124
- # Loam::Membership.tenants_for), stamping locked_until so a concurrent tick
124
+ # OpenLoam::Membership.tenants_for), stamping locked_until so a concurrent tick
125
125
  # skips them.
126
126
  def claim_due(now)
127
- lock_until = now + Loam::ScheduledJob::LOCK_TTL
128
- due = Loam::ScheduledJob.unscoped.active
127
+ lock_until = now + OpenLoam::ScheduledJob::LOCK_TTL
128
+ due = OpenLoam::ScheduledJob.unscoped.active
129
129
  .where("next_run_at <= ?", now)
130
130
  .where("locked_until IS NULL OR locked_until < ?", now)
131
131
 
132
132
  claimed = []
133
- Loam::ScheduledJob.transaction do
133
+ OpenLoam::ScheduledJob.transaction do
134
134
  relation = postgres? ? due.lock("FOR UPDATE SKIP LOCKED") : due
135
135
  ids = relation.pluck(:id)
136
136
  break if ids.empty?
137
137
 
138
- Loam::ScheduledJob.unscoped.where(id: ids).update_all(locked_until: lock_until)
139
- claimed = Loam::ScheduledJob.unscoped.where(id: ids).to_a
138
+ OpenLoam::ScheduledJob.unscoped.where(id: ids).update_all(locked_until: lock_until)
139
+ claimed = OpenLoam::ScheduledJob.unscoped.where(id: ids).to_a
140
140
  end
141
141
  claimed
142
142
  end
143
143
 
144
144
  def postgres?
145
- Loam::ScheduledJob.connection.adapter_name.to_s.downcase.include?("postgres")
145
+ OpenLoam::ScheduledJob.connection.adapter_name.to_s.downcase.include?("postgres")
146
146
  end
147
147
 
148
148
  def logger
@@ -1,4 +1,4 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Search
3
3
  # The driver contract. Subclass and override the class methods.
4
4
  #
@@ -10,10 +10,10 @@ module Loam
10
10
  #
11
11
  # index(record) / remove(record)
12
12
  # Maintain the driver's index as records are saved and destroyed. Called
13
- # from Loam::Searchable's after_save / after_destroy.
13
+ # from OpenLoam::Searchable's after_save / after_destroy.
14
14
  #
15
15
  # reindex(model)
16
- # Rebuild the index for a model (used by `loam:search:reindex` and seeds).
16
+ # Rebuild the index for a model (used by `open_loam:search:reindex` and seeds).
17
17
  #
18
18
  # A driver that needs no index (LIKE) leaves index/remove/reindex as no-ops.
19
19
  class Driver
@@ -1,24 +1,24 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Search
3
3
  # The default driver: a substring LIKE across the declared columns — exactly
4
- # the original Loam::Searchable behavior, now behind the seam. Portable
4
+ # the original OpenLoam::Searchable behavior, now behind the seam. Portable
5
5
  # (Arel's `matches` emits ILIKE on PostgreSQL, case-insensitive LIKE on
6
6
  # SQLite for ASCII) and needs no index, so index/remove/reindex are no-ops.
7
7
  #
8
8
  # What it does NOT do — ignore accents, split on words, rank — is the reason
9
- # the seam exists: swap in Loam::Search::TokenDriver (or an external engine)
9
+ # the seam exists: swap in OpenLoam::Search::TokenDriver (or an external engine)
10
10
  # and every `Model.search(q)` call site keeps working unchanged.
11
11
  class LikeDriver < Driver
12
12
  class << self
13
13
  def search(model, query, scope:)
14
14
  query = query.to_s.strip
15
- return scope if query.blank? || !model.loam_searchable?
15
+ return scope if query.blank? || !model.open_loam_searchable?
16
16
 
17
17
  # sanitize_sql_like escapes the LIKE wildcards so a literal % or _ in
18
18
  # the query stays literal; the second arg to `matches` declares that
19
19
  # escape character to the database (SQLite has none by default).
20
20
  pattern = "%#{model.sanitize_sql_like(query)}%"
21
- conditions = model.loam_searchable_columns.map do |column|
21
+ conditions = model.open_loam_searchable_columns.map do |column|
22
22
  model.arel_table[column].matches(pattern, "\\")
23
23
  end
24
24
  scope.where(conditions.inject(:or))
@@ -1,7 +1,7 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Search
3
3
  # A portable, word-level index (the flagship second driver). Each record's
4
- # searchable text is normalized into tokens stored in loam_search_tokens;
4
+ # searchable text is normalized into tokens stored in open_loam_search_tokens;
5
5
  # a query is tokenized the same way and matched with AND semantics — a record
6
6
  # must carry EVERY query token — so "excavator cat" finds "CAT 320 Excavator"
7
7
  # regardless of word order or position. Plain SQL: works on SQLite and
@@ -9,21 +9,21 @@ module Loam
9
9
  # order-independent). When an app outgrows even this, the same seam takes a
10
10
  # Meilisearch/Elasticsearch driver with no call-site change.
11
11
  #
12
- # Tenant isolation is free: Loam::SearchToken is a TenantRecord, so both the
12
+ # Tenant isolation is free: OpenLoam::SearchToken is a TenantRecord, so both the
13
13
  # match subquery and every write are scoped to the current tenant.
14
14
  class TokenDriver < Driver
15
15
  class << self
16
16
  def search(model, query, scope:)
17
17
  tokens = tokenize(query)
18
- return scope if tokens.empty? || !model.loam_searchable?
18
+ return scope if tokens.empty? || !model.open_loam_searchable?
19
19
 
20
20
  # AND semantics: keep only records carrying all N distinct query
21
21
  # tokens. Under AND every hit has the full match count, so ranking is
22
22
  # degenerate — ordering is left to the caller.
23
- matches = Loam::SearchToken
23
+ matches = OpenLoam::SearchToken
24
24
  .where(searchable_type: type_for(model), token: tokens)
25
25
  .group(:searchable_id)
26
- .having("COUNT(DISTINCT token) = #{tokens.size}")
26
+ .having("COUNT(DISTINCT token) = ?", tokens.size)
27
27
  .select(:searchable_id)
28
28
 
29
29
  scope.where(id: matches)
@@ -33,10 +33,10 @@ module Loam
33
33
  # the record's tenant, so the token rows land in the right tenant.
34
34
  def index(record)
35
35
  model = record.class
36
- return unless model.loam_searchable?
36
+ return unless model.open_loam_searchable?
37
37
 
38
38
  type = type_for(model)
39
- Loam::SearchToken.where(searchable_type: type, searchable_id: record.id).delete_all
39
+ OpenLoam::SearchToken.where(searchable_type: type, searchable_id: record.id).delete_all
40
40
 
41
41
  tokens = tokenize(searchable_text(record))
42
42
  return if tokens.empty?
@@ -44,17 +44,17 @@ module Loam
44
44
  rows = tokens.map do |token|
45
45
  { tenant_id: record.tenant_id, searchable_type: type, searchable_id: record.id, token: token }
46
46
  end
47
- Loam::SearchToken.insert_all(rows)
47
+ OpenLoam::SearchToken.insert_all(rows)
48
48
  end
49
49
 
50
50
  def remove(record)
51
- Loam::SearchToken.where(searchable_type: type_for(record.class), searchable_id: record.id).delete_all
51
+ OpenLoam::SearchToken.where(searchable_type: type_for(record.class), searchable_id: record.id).delete_all
52
52
  end
53
53
 
54
54
  # Rebuild every current-tenant record of a model — run inside
55
- # Loam.as_tenant per tenant (the loam:search:reindex task does that).
55
+ # OpenLoam.as_tenant per tenant (the open_loam:search:reindex task does that).
56
56
  def reindex(model)
57
- Loam::SearchToken.where(searchable_type: type_for(model)).delete_all
57
+ OpenLoam::SearchToken.where(searchable_type: type_for(model)).delete_all
58
58
  model.find_each { |record| index(record) }
59
59
  end
60
60
 
@@ -65,13 +65,13 @@ module Loam
65
65
  # THE SAFETY BOUNDARY: an encrypted column is skipped. Its stored value
66
66
  # is ciphertext (meaningless to tokenize), and tokenizing the PLAINTEXT
67
67
  # into a searchable table would recreate exactly the leak
68
- # Loam::Encryptable closes — an encrypted field stays exact-match-only via
68
+ # OpenLoam::Encryptable closes — an encrypted field stays exact-match-only via
69
69
  # its blind index. `searchable_by` already refuses to declare an
70
70
  # encrypted column; this subtraction is the second line of that defense.
71
71
  def searchable_text(record)
72
- columns = record.class.loam_searchable_columns
73
- if record.class.respond_to?(:loam_encrypted_attributes)
74
- columns -= record.class.loam_encrypted_attributes
72
+ columns = record.class.open_loam_searchable_columns
73
+ if record.class.respond_to?(:open_loam_encrypted_attributes)
74
+ columns -= record.class.open_loam_encrypted_attributes
75
75
  end
76
76
  columns.map { |column| record.public_send(column) }.join(" ")
77
77
  end
@@ -1,21 +1,21 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # The search seam. `Model.search(q)` and the `searchable_by` declaration never
3
3
  # change; the STRATEGY behind them is a swappable driver:
4
4
  #
5
- # Loam::Search.driver = Loam::Search::TokenDriver # in an initializer
5
+ # OpenLoam::Search.driver = OpenLoam::Search::TokenDriver # in an initializer
6
6
  #
7
7
  # The default is the LIKE driver — portable and zero-setup — so nothing
8
8
  # changes out of the box. A driver turns a text query into a subset of an
9
9
  # already-tenant-scoped relation and maintains whatever index it needs; see
10
- # Loam::Search::Driver for the contract. This is the same shape as
11
- # Loam::EventStream.broadcaster and Loam::Enrichers: one seam, drivers behind
10
+ # OpenLoam::Search::Driver for the contract. This is the same shape as
11
+ # OpenLoam::EventStream.broadcaster and OpenLoam::Enrichers: one seam, drivers behind
12
12
  # it, callers untouched.
13
13
  module Search
14
14
  class << self
15
15
  attr_writer :driver
16
16
 
17
17
  def driver
18
- @driver ||= Loam::Search::LikeDriver
18
+ @driver ||= OpenLoam::Search::LikeDriver
19
19
  end
20
20
 
21
21
  def search(model, query, scope:) = driver.search(model, query, scope: scope)