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,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,18 +9,18 @@ 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
26
  .having("COUNT(DISTINCT token) = #{tokens.size}")
@@ -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)
@@ -1,8 +1,8 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Text search over an entity's own columns:
3
3
  #
4
- # class Equipment < Loam::TenantRecord
5
- # include Loam::Searchable
4
+ # class Equipment < OpenLoam::TenantRecord
5
+ # include OpenLoam::Searchable
6
6
  # searchable_by :name, :status
7
7
  # end
8
8
  #
@@ -12,19 +12,19 @@ module Loam
12
12
  # to remember: the default scope is already on it, and the result composes
13
13
  # with ordering, pagination and anything else.
14
14
  #
15
- # HOW a query matches is a swappable driver (Loam::Search.driver): the default
15
+ # HOW a query matches is a swappable driver (OpenLoam::Search.driver): the default
16
16
  # LikeDriver is a substring LIKE; the TokenDriver is a portable word-level
17
17
  # index; an external engine is a third. This concern is only the DECLARATION
18
18
  # (`searchable_by`) and the untouched call site (`Model.search(q)`) — swapping
19
- # the driver changes neither. See Loam::Search.
19
+ # the driver changes neither. See OpenLoam::Search.
20
20
  module Searchable
21
21
  extend ActiveSupport::Concern
22
22
 
23
23
  included do
24
- class_attribute :loam_searchable_columns, default: [].freeze, instance_writer: false
24
+ class_attribute :open_loam_searchable_columns, default: [].freeze, instance_writer: false
25
25
 
26
26
  # Keep the active driver's index current. A no-op under the default
27
- # LikeDriver; the TokenDriver maintains loam_search_tokens here. Guarded so
27
+ # LikeDriver; the TokenDriver maintains open_loam_search_tokens here. Guarded so
28
28
  # non-searchable models pay nothing, and skipped when no searchable column
29
29
  # actually changed — so a soft-delete (which touches only deleted_at)
30
30
  # leaves the tokens in place, and the base scope hides the row anyway.
@@ -33,8 +33,8 @@ module Loam
33
33
  # exercise the `searchable_by` DSL (its encrypted-field check), with no
34
34
  # ActiveRecord underneath — only a real model gets the callbacks.
35
35
  if respond_to?(:after_save)
36
- after_save :loam_update_search_index, if: :loam_search_index_stale?
37
- after_destroy :loam_remove_from_search_index, if: -> { self.class.loam_searchable? }
36
+ after_save :open_loam_update_search_index, if: :open_loam_search_index_stale?
37
+ after_destroy :open_loam_remove_from_search_index, if: -> { self.class.open_loam_searchable? }
38
38
  end
39
39
  end
40
40
 
@@ -45,24 +45,24 @@ module Loam
45
45
  def searchable_by(*columns)
46
46
  columns = columns.map(&:to_s)
47
47
 
48
- # The mirror of Loam::Encryptable's check: an encrypted column is
48
+ # The mirror of OpenLoam::Encryptable's check: an encrypted column is
49
49
  # ciphertext at rest, which LIKE cannot match. Caught here when
50
50
  # `searchable_by` is the later declaration (encrypts catches the reverse).
51
- if respond_to?(:loam_encrypted_attributes)
52
- conflict = columns & loam_encrypted_attributes
51
+ if respond_to?(:open_loam_encrypted_attributes)
52
+ conflict = columns & open_loam_encrypted_attributes
53
53
  if conflict.any?
54
- raise Loam::Error,
54
+ raise OpenLoam::Error,
55
55
  "#{name}: cannot `searchable_by` encrypted field(s) #{conflict.join(', ')} — " \
56
56
  "ciphertext cannot be LIKE-searched. Use `encrypts :field, searchable: true` " \
57
57
  "for exact-match lookup instead."
58
58
  end
59
59
  end
60
60
 
61
- self.loam_searchable_columns = columns.freeze
61
+ self.open_loam_searchable_columns = columns.freeze
62
62
  end
63
63
 
64
- def loam_searchable?
65
- loam_searchable_columns.any?
64
+ def open_loam_searchable?
65
+ open_loam_searchable_columns.any?
66
66
  end
67
67
 
68
68
  # Delegates to the active driver, handing it the current relation as the
@@ -70,21 +70,21 @@ module Loam
70
70
  # (a class method called on a relation runs with `all` == that relation).
71
71
  # A blank query returns everything: an empty search box means "no filter".
72
72
  def search(query)
73
- Loam::Search.search(self, query, scope: all)
73
+ OpenLoam::Search.search(self, query, scope: all)
74
74
  end
75
75
  end
76
76
 
77
77
  private
78
78
 
79
- def loam_update_search_index = Loam::Search.index(self)
80
- def loam_remove_from_search_index = Loam::Search.remove(self)
79
+ def open_loam_update_search_index = OpenLoam::Search.index(self)
80
+ def open_loam_remove_from_search_index = OpenLoam::Search.remove(self)
81
81
 
82
82
  # On create every column counts as changed, so a new record is always
83
83
  # indexed; on update, reindex only when a searchable column actually moved.
84
- def loam_search_index_stale?
85
- return false unless self.class.loam_searchable?
84
+ def open_loam_search_index_stale?
85
+ return false unless self.class.open_loam_searchable?
86
86
 
87
- saved_changes.keys.intersect?(self.class.loam_searchable_columns)
87
+ saved_changes.keys.intersect?(self.class.open_loam_searchable_columns)
88
88
  end
89
89
  end
90
90
  end
@@ -1,7 +1,7 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Soft-delete: destroying a business record hides it instead of erasing it.
3
3
  # A recycle bin — for GDPR retention, for undo, for audits that still read
4
- # cleanly after the fact. Loam is "safe by default", so a soft-deleted record
4
+ # cleanly after the fact. OpenLoam is "safe by default", so a soft-deleted record
5
5
  # is EXCLUDED from every ordinary query; seeing it is something you opt INTO,
6
6
  # never a filter you must remember to add.
7
7
  #
@@ -34,19 +34,19 @@ module Loam
34
34
  end
35
35
 
36
36
  def soft_delete
37
- loam_toggle_deleted(deleted: true, bang: false)
37
+ open_loam_toggle_deleted(deleted: true, bang: false)
38
38
  end
39
39
 
40
40
  def soft_delete!
41
- loam_toggle_deleted(deleted: true, bang: true)
41
+ open_loam_toggle_deleted(deleted: true, bang: true)
42
42
  end
43
43
 
44
44
  def restore
45
- loam_toggle_deleted(deleted: false, bang: false)
45
+ open_loam_toggle_deleted(deleted: false, bang: false)
46
46
  end
47
47
 
48
48
  def restore!
49
- loam_toggle_deleted(deleted: false, bang: true)
49
+ open_loam_toggle_deleted(deleted: false, bang: true)
50
50
  end
51
51
 
52
52
  private
@@ -55,18 +55,18 @@ module Loam
55
55
  # record to the state it is already in (a double-clicked button) is a no-op,
56
56
  # so the trail never fills with deleted_at -> deleted_at noise.
57
57
  #
58
- # A soft-delete is an UPDATE, so Loam::Auditable's after_destroy never fires
58
+ # A soft-delete is an UPDATE, so OpenLoam::Auditable's after_destroy never fires
59
59
  # for it. Rather than write our own audit row we borrow Auditable's single
60
- # audit path via `loam_audit_as`, which relabels this save's audit
60
+ # audit path via `open_loam_audit_as`, which relabels this save's audit
61
61
  # "soft_delete"/"restore" instead of the "update" it would otherwise record.
62
- def loam_toggle_deleted(deleted:, bang:)
62
+ def open_loam_toggle_deleted(deleted:, bang:)
63
63
  return true if deleted == deleted?
64
64
 
65
65
  self.deleted_at = deleted ? Time.current : nil
66
66
  action = deleted ? "soft_delete" : "restore"
67
67
  write = -> { bang ? save! : save }
68
68
 
69
- respond_to?(:loam_audit_as, true) ? loam_audit_as(action, &write) : write.call
69
+ respond_to?(:open_loam_audit_as, true) ? open_loam_audit_as(action, &write) : write.call
70
70
  end
71
71
  end
72
72
  end
@@ -1,4 +1,4 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Sso
3
3
  # Normalized identity claims, protocol-independent. Every provider's
4
4
  # `exchange` returns one of these so the rest of the flow never knows whether
@@ -1,6 +1,6 @@
1
1
  require "base64"
2
2
 
3
- module Loam
3
+ module OpenLoam
4
4
  module Sso
5
5
  # An OFFLINE stand-in for a real IdP — for the demo and the test suite ONLY,
6
6
  # NEVER production. `authorization_url` loops straight back to our own
@@ -1,10 +1,10 @@
1
1
  require "net/http"
2
2
  require "json"
3
3
 
4
- module Loam
4
+ module OpenLoam
5
5
  module Sso
6
6
  # The one place SSO talks to the network — kept tiny and isolated so the rest
7
- # of the flow is pure. Only Loam::Sso::OidcProvider uses it, and the test
7
+ # of the flow is pure. Only OpenLoam::Sso::OidcProvider uses it, and the test
8
8
  # suite injects a FakeProvider instead, so this is never exercised offline.
9
9
  module HttpClient
10
10
  module_function
@@ -32,7 +32,7 @@ module Loam
32
32
 
33
33
  def parse(response)
34
34
  unless response.is_a?(Net::HTTPSuccess)
35
- raise Loam::Sso::Error, "identity provider returned #{response.code}"
35
+ raise OpenLoam::Sso::Error, "identity provider returned #{response.code}"
36
36
  end
37
37
 
38
38
  JSON.parse(response.body)
@@ -1,4 +1,4 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Sso
3
3
  # The real OIDC Authorization Code provider. Discovers the issuer's endpoints
4
4
  # from its .well-known document, sends the user to the authorization endpoint,
@@ -13,7 +13,7 @@ module Loam
13
13
  # JWKS verification for the prototype. Roadmap: full id_token + JWKS.
14
14
  #
15
15
  # NOT exercised by the test suite (a FakeProvider is injected via
16
- # Loam::Sso.builder); live OIDC against a real IdP is verified manually.
16
+ # OpenLoam::Sso.builder); live OIDC against a real IdP is verified manually.
17
17
  class OidcProvider
18
18
  SCOPE = "openid email profile".freeze
19
19
 
@@ -1,25 +1,25 @@
1
- require "loam/sso/claims"
2
- require "loam/sso/http_client"
3
- require "loam/sso/oidc_provider"
4
- require "loam/sso/fake_provider"
1
+ require "open_loam/sso/claims"
2
+ require "open_loam/sso/http_client"
3
+ require "open_loam/sso/oidc_provider"
4
+ require "open_loam/sso/fake_provider"
5
5
 
6
- module Loam
6
+ module OpenLoam
7
7
  # Single sign-on. SHIPPED: OIDC Authorization Code flow end-to-end —
8
8
  # home-realm discovery by email domain, just-in-time user provisioning, IdP
9
9
  # group -> role mapping, and account linking to an existing User. SEAMS
10
10
  # (documented, not built): SAML (another protocol behind the same interface,
11
11
  # raises NotImplementedError until implemented) and SCIM 2.0 provisioning
12
- # (see docs/_foundation/overview.md). See Loam::SsoProvider for the per-tenant config.
12
+ # (see docs/_foundation/overview.md). See OpenLoam::SsoProvider for the per-tenant config.
13
13
  #
14
14
  # A protocol provider implements two methods:
15
15
  # authorization_url(state:, login_hint: nil) -> the IdP URL to redirect to
16
- # exchange(code:) -> Loam::Sso::Claims
16
+ # exchange(code:) -> OpenLoam::Sso::Claims
17
17
  #
18
18
  # The provider is built through a swappable `builder` so tests and the demo
19
- # inject a FakeProvider and NOTHING touches the network. Loam::Sso::OidcProvider
19
+ # inject a FakeProvider and NOTHING touches the network. OpenLoam::Sso::OidcProvider
20
20
  # is the real one; it is never constructed in the test suite.
21
21
  module Sso
22
- class Error < Loam::Error; end
22
+ class Error < OpenLoam::Error; end
23
23
 
24
24
  # The identity provider did not assert a verified email. Linking or creating
25
25
  # an account on an unverified email is an account-takeover vector, so the
@@ -39,7 +39,7 @@ module Loam
39
39
  @builder ||= method(:default_builder)
40
40
  end
41
41
 
42
- # Wrap a Loam::SsoProvider record in its protocol provider.
42
+ # Wrap a OpenLoam::SsoProvider record in its protocol provider.
43
43
  def build(record, redirect_uri:)
44
44
  builder.call(record, redirect_uri)
45
45
  end
@@ -50,25 +50,25 @@ module Loam
50
50
  OidcProvider.new(record, redirect_uri: redirect_uri)
51
51
  when "saml"
52
52
  raise NotImplementedError,
53
- "SAML SSO is a documented seam — implement Loam::Sso::SamlProvider behind this interface."
53
+ "SAML SSO is a documented seam — implement OpenLoam::Sso::SamlProvider behind this interface."
54
54
  else
55
55
  raise Error, "Unsupported SSO protocol: #{record.protocol.inspect}"
56
56
  end
57
57
  end
58
58
 
59
59
  # Home-realm discovery. THE blessed cross-tenant lookup for SSO (like
60
- # Loam::Membership.tenants_for): "which tenant's IdP owns this email
60
+ # OpenLoam::Membership.tenants_for): "which tenant's IdP owns this email
61
61
  # domain?" is asked at the sign-in page, before any tenant is chosen, so it
62
62
  # reaches across tenants via `unscoped` — something host code must never do.
63
63
  def provider_for(email:)
64
64
  domain = email.to_s.split("@").last.to_s.strip.downcase
65
65
  return nil if domain.blank?
66
66
 
67
- Loam::SsoProvider.unscoped.where(domain: domain, active: true).first
67
+ OpenLoam::SsoProvider.unscoped.where(domain: domain, active: true).first
68
68
  end
69
69
 
70
70
  # Resolve verified IdP claims to a signed-in User, in the provider's tenant.
71
- # MUST be called inside Loam.as_tenant(provider.tenant). Order matters:
71
+ # MUST be called inside OpenLoam.as_tenant(provider.tenant). Order matters:
72
72
  # match the durable (provider, sub) identity first, then an existing User by
73
73
  # verified email (link), else just-in-time create — always with a tenant
74
74
  # membership at the mapped role.
@@ -86,13 +86,13 @@ module Loam
86
86
 
87
87
  # Resolve the user: the durable (provider, sub) identity first, then an
88
88
  # existing User by verified email (link), else just-in-time create.
89
- identity = Loam::SsoIdentity.find_by(sso_provider_id: provider.id, sub: claims.sub)
89
+ identity = OpenLoam::SsoIdentity.find_by(sso_provider_id: provider.id, sub: claims.sub)
90
90
  user = identity&.user || User.find_by(email: claims.email) || jit_create_user(claims)
91
91
 
92
92
  # Re-map claims -> role on EVERY login (including a returning identity),
93
93
  # so an IdP role change takes effect.
94
94
  ensure_membership(provider, user, claims)
95
- Loam::SsoIdentity.create!(user: user, sso_provider: provider, sub: claims.sub) unless identity
95
+ OpenLoam::SsoIdentity.create!(user: user, sso_provider: provider, sub: claims.sub) unless identity
96
96
  user
97
97
  end
98
98
 
@@ -120,7 +120,7 @@ module Loam
120
120
  # (a promotion reflects, a removed group downgrades) rather than freezing
121
121
  # at whatever the role was when the membership was first created.
122
122
  def ensure_membership(provider, user, claims)
123
- membership = Loam::Membership.find_or_initialize_by(user_id: user.id)
123
+ membership = OpenLoam::Membership.find_or_initialize_by(user_id: user.id)
124
124
  membership.role = role_for(provider, claims)
125
125
  membership.save!
126
126
  end
@@ -1,16 +1,16 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # A dependency-free observability seam (L-712). Wrap a unit of work in a span;
3
3
  # by default it emits an `ActiveSupport::Notifications` event
4
- # ("loam.span.<name>") carrying the attributes and duration, so ANY subscriber
4
+ # ("open_loam.span.<name>") carrying the attributes and duration, so ANY subscriber
5
5
  # — an OpenTelemetry bridge, StatsD, a log line — picks it up with no hard
6
- # dependency on Loam's side. An app that wants real OTLP spans replaces the
6
+ # dependency on OpenLoam's side. An app that wants real OTLP spans replaces the
7
7
  # whole strategy:
8
8
  #
9
- # Loam::Telemetry.backend = ->(name, attributes, work) do
9
+ # OpenLoam::Telemetry.backend = ->(name, attributes, work) do
10
10
  # Tracer.in_span(name, attributes: attributes) { work.call }
11
11
  # end
12
12
  #
13
- # Loam instruments its async/background hot paths (scheduler tick, durable
13
+ # OpenLoam instruments its async/background hot paths (scheduler tick, durable
14
14
  # event delivery, inbound webhook ingest) so an operator sees them without
15
15
  # wiring anything per-call.
16
16
  module Telemetry
@@ -24,7 +24,7 @@ module Loam
24
24
  if backend
25
25
  backend.call(name.to_s, attributes, block)
26
26
  else
27
- ActiveSupport::Notifications.instrument("loam.span.#{name}", **attributes, &block)
27
+ ActiveSupport::Notifications.instrument("open_loam.span.#{name}", **attributes, &block)
28
28
  end
29
29
  end
30
30