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,21 +1,21 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # A staged mutation awaiting human approval. When a caller runs under
3
- # confirm-mode (an MCP tool acting for an AI agent — see Loam.mutation_mode), a
3
+ # confirm-mode (an MCP tool acting for an AI agent — see OpenLoam.mutation_mode), a
4
4
  # write is staged HERE as a preview instead of committing; a manager approves
5
- # or rejects, and only approval executes it. This is Loam's thesis made
5
+ # or rejects, and only approval executes it. This is OpenLoam's thesis made
6
6
  # concrete: agent-triggered writes are gate-able.
7
7
  #
8
- # The approval gate IS a workflow (Loam::Workflow): pending → (manager) approve
8
+ # The approval gate IS a workflow (OpenLoam::Workflow): pending → (manager) approve
9
9
  # / reject → executed / failed. "Who may approve" lives in one declarative,
10
- # role-gated place. The proposed changes are encrypted at rest (Loam::Encryptable),
10
+ # role-gated place. The proposed changes are encrypted at rest (OpenLoam::Encryptable),
11
11
  # because a staged change to an encrypted target field would otherwise sit here
12
12
  # — and in this row's own audit — as plaintext, reopening the leak L-901 closed.
13
- class PendingAction < Loam::TenantRecord
14
- self.table_name = "loam_pending_actions"
13
+ class PendingAction < OpenLoam::TenantRecord
14
+ self.table_name = "open_loam_pending_actions"
15
15
 
16
- include Loam::Auditable
17
- include Loam::Encryptable
18
- include Loam::Workflow
16
+ include OpenLoam::Auditable
17
+ include OpenLoam::Encryptable
18
+ include OpenLoam::Workflow
19
19
 
20
20
  belongs_to :actor, class_name: "User", optional: true
21
21
  belongs_to :reviewer, class_name: "User", foreign_key: :reviewed_by_id, optional: true
@@ -96,7 +96,7 @@ module Loam
96
96
  # write; the record ends "executed" or "failed". Executes as `by`, so the
97
97
  # TARGET's audit names the approving human — the person owns the change.
98
98
  def approve!(by:)
99
- Loam.as_tenant(tenant, actor: by) do
99
+ OpenLoam.as_tenant(tenant, actor: by) do
100
100
  reject_self_approval!(by)
101
101
  self.reviewed_by_id = by.id
102
102
  self.reviewed_at = Time.current
@@ -107,7 +107,7 @@ module Loam
107
107
  end
108
108
 
109
109
  def reject!(by:, reason: nil)
110
- Loam.as_tenant(tenant, actor: by) do
110
+ OpenLoam.as_tenant(tenant, actor: by) do
111
111
  self.reviewed_by_id = by.id
112
112
  self.reviewed_at = Time.current
113
113
  self.result = [ "rejected", reason.presence ].compact.join(": ")
@@ -126,9 +126,9 @@ module Loam
126
126
  # withdrawal, not a segregation-of-duties concern.)
127
127
  def reject_self_approval!(by)
128
128
  return unless by.id == actor_id
129
- return if Loam::Configs.get("approvals.allow_self_approve", default: false)
129
+ return if OpenLoam::Configs.get("approvals.allow_self_approve", default: false)
130
130
 
131
- raise Loam::NotAuthorizedError, "self-approval is not permitted; a different person must approve this change"
131
+ raise OpenLoam::NotAuthorizedError, "self-approval is not permitted; a different person must approve this change"
132
132
  end
133
133
 
134
134
  # Execution AND its status/result recording share ONE transaction with the
@@ -150,25 +150,69 @@ module Loam
150
150
  update!(error: error.message)
151
151
  end
152
152
 
153
+ # Never settable through a staged change: soft-delete state has its own
154
+ # audited action (and its own policy question), and the rest is plumbing.
155
+ PROTECTED_FIELDS = %w[id tenant_id deleted_at lock_version].freeze
156
+
153
157
  def execute_change!
154
158
  case action_type
155
159
  when "create"
156
- record = target_class.create!(changeset)
160
+ record = target_class.new
161
+ authorize_action!(record, :create?)
162
+ record.assign_attributes(permitted_changeset(record))
163
+ record.save!
157
164
  "created #{target_type}##{record.id}"
158
165
  when "update"
159
- target_class.find(target_id).update!(changeset)
166
+ record = target_class.find(target_id)
167
+ authorize_action!(record, :update?)
168
+ record.update!(permitted_changeset(record))
160
169
  "updated #{target_type}##{target_id}"
161
170
  when "destroy"
162
171
  target = target_class.find(target_id)
172
+ authorize_action!(target, :destroy?)
163
173
  target.respond_to?(:soft_delete!) ? target.soft_delete! : target.destroy!
164
174
  "deleted #{target_type}##{target_id}"
165
175
  else
166
- raise Loam::Error, "unknown action_type #{action_type.inspect}"
176
+ raise OpenLoam::Error, "unknown action_type #{action_type.inspect}"
167
177
  end
168
178
  end
169
179
 
180
+ # The field rules below are not the whole policy: a host may restrict the
181
+ # ACTION itself (destroy? to an owner, say). Staging must not route around
182
+ # that, so the approver's verdict on the action is asked first.
183
+ def authorize_action!(record, question)
184
+ return if OpenLoam::Policy.for(record).public_send(question)
185
+
186
+ raise OpenLoam::NotAuthorizedError,
187
+ "the approver may not #{question.to_s.chomp('?')} #{target_type}"
188
+ end
189
+
190
+ # target_type is a string off a stored row, so it gets the same treatment as
191
+ # any other type resolution from untrusted input. The TenantRecord check is
192
+ # the load-bearing half: a non-tenant-scoped target (User, say) has no
193
+ # default scope, which would turn approval into a cross-tenant write.
170
194
  def target_class
171
- target_type.constantize
195
+ klass = target_type.to_s.safe_constantize
196
+ unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord
197
+ raise OpenLoam::Error, "#{target_type.inspect} is not a tenant-scoped model — refusing to apply a staged change"
198
+ end
199
+
200
+ klass
201
+ end
202
+
203
+ # Staging is deliberately ungated ("propose" is not "apply"), so approval is
204
+ # where authority is spent and the APPROVER's field rules are what bind —
205
+ # otherwise staging routes around every one of them. Refusals raise rather
206
+ # than drop, so the proposal fails visibly instead of applying a lesser change.
207
+ def permitted_changeset(record)
208
+ blocked = changeset.keys & PROTECTED_FIELDS
209
+ raise OpenLoam::Error, "a staged change may not set #{blocked.join(', ')}" if blocked.any?
210
+
211
+ policy = OpenLoam::Policy.for(record)
212
+ refused = changeset.keys - policy.permitted_fields(changeset.keys).map(&:to_s)
213
+ raise OpenLoam::NotAuthorizedError, "the approver may not write #{refused.join(', ')}" if refused.any?
214
+
215
+ changeset
172
216
  end
173
217
 
174
218
  def load_target
@@ -178,31 +222,31 @@ module Loam
178
222
  end
179
223
 
180
224
  def encrypted_target_fields
181
- return [] unless target_class.respond_to?(:loam_encrypted_attributes)
225
+ return [] unless target_class.respond_to?(:open_loam_encrypted_attributes)
182
226
 
183
- target_class.loam_encrypted_attributes.map(&:to_s)
227
+ target_class.open_loam_encrypted_attributes.map(&:to_s)
184
228
  end
185
229
 
186
230
  # Set while a workflow transition is performing its save, so the validation
187
231
  # below can tell a legitimate status move from a direct assignment.
188
- def loam_perform_transition!(transition)
189
- @loam_status_via_transition = true
232
+ def open_loam_perform_transition!(transition)
233
+ @open_loam_status_via_transition = true
190
234
  super
191
235
  ensure
192
- @loam_status_via_transition = false
236
+ @open_loam_status_via_transition = false
193
237
  end
194
238
 
195
239
  def status_changes_only_through_a_transition
196
240
  return unless will_save_change_to_status?
197
- return if @loam_status_via_transition
241
+ return if @open_loam_status_via_transition
198
242
 
199
243
  errors.add(:status, "may only change through an approval transition (approve!/reject!), not a direct write")
200
244
  end
201
245
 
202
246
  def status_starts_at_the_initial_state
203
- return if status == self.class.loam_workflow.initial
247
+ return if status == self.class.open_loam_workflow.initial
204
248
 
205
- errors.add(:status, "must start at #{self.class.loam_workflow.initial.inspect} — a staged action begins pending")
249
+ errors.add(:status, "must start at #{self.class.open_loam_workflow.initial.inspect} — a staged action begins pending")
206
250
  end
207
251
  end
208
252
  end
@@ -1,4 +1,4 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # A saved view of an entity's admin index — which columns show, active filters,
3
3
  # sort, and page size — that a user can name, make their default, or share.
4
4
  #
@@ -7,15 +7,15 @@ module Loam
7
7
  # * "role" — everyone with the named membership role in the tenant.
8
8
  # * "tenant" — everyone in the tenant.
9
9
  # A private view is invisible to others by construction (see
10
- # Loam::Perspectives.visible_to), not merely by a permission check.
10
+ # OpenLoam::Perspectives.visible_to), not merely by a permission check.
11
11
  #
12
12
  # Tenant-scoped like everything else, and audited. Optimistic-locked
13
13
  # (lock_version) so two people editing a shared view don't silently clobber
14
14
  # each other — the admin controller rescues ActiveRecord::StaleObjectError.
15
- class Perspective < Loam::TenantRecord
16
- self.table_name = "loam_perspectives"
15
+ class Perspective < OpenLoam::TenantRecord
16
+ self.table_name = "open_loam_perspectives"
17
17
 
18
- include Loam::Auditable
18
+ include OpenLoam::Auditable
19
19
 
20
20
  VISIBILITIES = %w[private role tenant].freeze
21
21
 
@@ -1,4 +1,4 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Progress of a long-running job (a bulk import, a reindex, a report) so the
3
3
  # admin can watch it live instead of guessing. Tenant-scoped; a job started in
4
4
  # one tenant is only visible and streamable there.
@@ -7,11 +7,11 @@ module Loam
7
7
  # ticks) and the audit trail would fill with noise. The terminal status and
8
8
  # any error are captured on the row itself, which is the summary that matters.
9
9
  #
10
- # Each meaningful change publishes `loam.progress.updated` (broadcastable → SSE)
11
- # carrying only id/percent/status — no record contents. See Loam::Progress for
10
+ # Each meaningful change publishes `open_loam.progress.updated` (broadcastable → SSE)
11
+ # carrying only id/percent/status — no record contents. See OpenLoam::Progress for
12
12
  # the entry point.
13
- class ProgressJob < Loam::TenantRecord
14
- self.table_name = "loam_progress_jobs"
13
+ class ProgressJob < OpenLoam::TenantRecord
14
+ self.table_name = "open_loam_progress_jobs"
15
15
 
16
16
  STATUSES = %w[running completed failed cancelled].freeze
17
17
  STALE_AFTER = 5.minutes
@@ -93,11 +93,11 @@ module Loam
93
93
  self
94
94
  end
95
95
 
96
- # Only ever id/percent/status leave the server (Loam::Events stamps tenant_id
96
+ # Only ever id/percent/status leave the server (OpenLoam::Events stamps tenant_id
97
97
  # for the SSE deliverable filter; the frame's safe_payload drops everything
98
98
  # else).
99
99
  def broadcast
100
- Loam::Events.publish("loam.progress.updated", id: id, percent: percent, status: status)
100
+ OpenLoam::Events.publish("open_loam.progress.updated", id: id, percent: percent, status: status)
101
101
  end
102
102
  end
103
103
  end
@@ -1,12 +1,12 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # An advisory "someone is editing this" lock on a record — the COURTESY layer.
3
3
  # The real guarantee against a silent clobber is optimistic locking
4
- # (lock_version, see Loam::RecordLocks docs); this only warns the second
4
+ # (lock_version, see OpenLoam::RecordLocks docs); this only warns the second
5
5
  # editor. One lock per record, with a TTL: an expired lock is treated as free,
6
6
  # and re-acquiring as the same holder extends it (a heartbeat). Plumbing, so
7
- # not audited — like Loam::ApiToken.
8
- class RecordLock < Loam::TenantRecord
9
- self.table_name = "loam_record_locks"
7
+ # not audited — like OpenLoam::ApiToken.
8
+ class RecordLock < OpenLoam::TenantRecord
9
+ self.table_name = "open_loam_record_locks"
10
10
 
11
11
  belongs_to :locked_by, class_name: "User"
12
12
 
@@ -1,16 +1,16 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # A recurring job that runs on a schedule (cron or interval). Tenant-scoped:
3
3
  # a "tenant"-scope schedule enqueues its job in this tenant on schedule; a
4
4
  # "system"-scope schedule enqueues once with no tenant context. Audited on
5
5
  # config changes (not per run — runs are tracked by last_run_at/next_run_at).
6
6
  #
7
7
  # SECURITY: job_class is validated to be a known ActiveJob (see
8
- # Loam::Scheduler.resolve_job_class) at save AND at enqueue — a scheduler that
8
+ # OpenLoam::Scheduler.resolve_job_class) at save AND at enqueue — a scheduler that
9
9
  # constantized and ran arbitrary user input would be a code-execution hole.
10
- class ScheduledJob < Loam::TenantRecord
11
- self.table_name = "loam_scheduled_jobs"
10
+ class ScheduledJob < OpenLoam::TenantRecord
11
+ self.table_name = "open_loam_scheduled_jobs"
12
12
 
13
- include Loam::Auditable
13
+ include OpenLoam::Auditable
14
14
 
15
15
  SCOPES = %w[tenant system].freeze
16
16
  LOCK_TTL = 5.minutes # how long a claim holds a due job before another tick may retry it
@@ -26,11 +26,11 @@ module Loam
26
26
  # The next fire time strictly after `from`, in this job's timezone (UTC when
27
27
  # unset). Stored in next_run_at as UTC.
28
28
  def compute_next_run(from = Time.current)
29
- Loam::Cron.next_after(schedule, from: from, zone: timezone.presence || "UTC")
29
+ OpenLoam::Cron.next_after(schedule, from: from, zone: timezone.presence || "UTC")
30
30
  end
31
31
 
32
32
  def job_class_constant
33
- Loam::Scheduler.resolve_job_class(job_class)
33
+ OpenLoam::Scheduler.resolve_job_class(job_class)
34
34
  end
35
35
 
36
36
  private
@@ -0,0 +1,9 @@
1
+ module OpenLoam
2
+ # The word-level search index maintained by OpenLoam::Search::TokenDriver: one row
3
+ # per (record, token), tenant-scoped like every OpenLoam entity. Plumbing — not
4
+ # audited, not soft-deletable. Only the TokenDriver reads or writes it; under
5
+ # the default LikeDriver this table simply stays empty.
6
+ class SearchToken < OpenLoam::TenantRecord
7
+ self.table_name = "open_loam_search_tokens"
8
+ end
9
+ end
@@ -1,12 +1,12 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Links an external IdP identity (a provider's stable `sub`) to a local User.
3
3
  # The durable key SSO callbacks resolve on — an email can change at the IdP,
4
4
  # `sub` does not. Tenant-scoped (created and read inside the provider's tenant).
5
- class SsoIdentity < Loam::TenantRecord
6
- self.table_name = "loam_sso_identities"
5
+ class SsoIdentity < OpenLoam::TenantRecord
6
+ self.table_name = "open_loam_sso_identities"
7
7
 
8
8
  belongs_to :user
9
- belongs_to :sso_provider, class_name: "Loam::SsoProvider"
9
+ belongs_to :sso_provider, class_name: "OpenLoam::SsoProvider"
10
10
 
11
11
  validates :sub, presence: true, uniqueness: { scope: :sso_provider_id }
12
12
  end
@@ -0,0 +1,66 @@
1
+ module OpenLoam
2
+ # A tenant's connection to an external identity provider (OIDC). Per-tenant
3
+ # config: each tenant/org wires its OWN IdP, so the client_secret is encrypted
4
+ # under the tenant key (the default Encryptable scope) — unlike MFA, which is
5
+ # user-keyed. Home-realm discovery matches an email `domain` to the owning
6
+ # provider at the sign-in page (see OpenLoam::Sso.provider_for).
7
+ class SsoProvider < OpenLoam::TenantRecord
8
+ self.table_name = "open_loam_sso_providers"
9
+
10
+ include OpenLoam::Auditable # config changes are audited; the secret is redacted
11
+ include OpenLoam::Encryptable
12
+
13
+ # Tenant-scoped key (default): SSO config belongs to the tenant, and the
14
+ # secret is only ever read inside that tenant's context on callback.
15
+ encrypts :client_secret
16
+
17
+ has_many :identities, class_name: "OpenLoam::SsoIdentity",
18
+ foreign_key: :sso_provider_id, dependent: :delete_all
19
+
20
+ validates :name, :protocol, :domain, :jit_role, presence: true
21
+ # Globally unique so HRD resolves exactly one owning IdP per domain. Rails'
22
+ # uniqueness validator queries WITHOUT the default tenant scope, so this
23
+ # catches a collision across tenants too.
24
+ validates :domain, uniqueness: true
25
+
26
+ normalizes :domain, with: ->(domain) { domain.to_s.strip.downcase.presence }
27
+
28
+ # The server fetches the issuer's discovery document and posts the code
29
+ # exchange to it, so a manager-typed issuer is an SSRF vector unless it is
30
+ # provably external. https because the client_secret travels over it.
31
+ validate :issuer_is_reachable_from_outside, if: -> { issuer.present? }
32
+
33
+ # Editing the domain drops the proof — otherwise a verified provider could be
34
+ # repointed at a domain nobody approved.
35
+ before_validation :reset_domain_verification, if: -> { persisted? && domain_changed? }
36
+
37
+ scope :active, -> { where(active: true) }
38
+ scope :domain_verified, -> { where.not(domain_verified_at: nil) }
39
+
40
+ # Until ownership is proven, HRD skips the provider and OpenLoam::Sso refuses
41
+ # to link an existing account through it.
42
+ def domain_verified? = domain_verified_at.present?
43
+
44
+ # Operator entry point (rake open_loam:sso:verify_domain), off the admin path.
45
+ def verify_domain!(at: Time.current)
46
+ update!(domain_verified_at: at)
47
+ end
48
+
49
+ # IdP group -> OpenLoam role, first match wins; falls back to jit_role.
50
+ def group_roles
51
+ group_role_map.is_a?(Hash) ? group_role_map : {}
52
+ end
53
+
54
+ private
55
+
56
+ def reset_domain_verification
57
+ self.domain_verified_at = nil
58
+ end
59
+
60
+ def issuer_is_reachable_from_outside
61
+ OpenLoam::OutboundUrl.validate!(issuer, require_https: true)
62
+ rescue OpenLoam::OutboundUrl::BlockedError => error
63
+ errors.add(:issuer, error.message)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,20 @@
1
+ module OpenLoam
2
+ # The tenant itself is deliberately NOT tenant-scoped — it is the axis the
3
+ # rest of the system is scoped by.
4
+ class Tenant < ActiveRecord::Base
5
+ include OpenLoam::GeneratedKey
6
+ self.table_name = "open_loam_tenants"
7
+
8
+ validates :name, presence: true
9
+ validates :slug, presence: true, uniqueness: true
10
+
11
+ has_many :memberships, class_name: "OpenLoam::Membership", dependent: :delete_all
12
+
13
+ # A new tenant is never empty: whatever the app registered via
14
+ # OpenLoam.on_tenant_created runs here, inside this tenant's context. After
15
+ # commit, so callbacks see a persisted tenant and may enqueue jobs.
16
+ # `bin/rails open_loam:sync` re-runs the same callbacks for existing tenants —
17
+ # which is why they must be idempotent (see OpenLoam::Lifecycle).
18
+ after_create_commit { OpenLoam::Lifecycle.run_tenant_created(self) }
19
+ end
20
+ end
@@ -1,13 +1,13 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # A per-locale override for ONE field of ONE record — the storage behind
3
- # Loam::Translatable. The record's own column holds the base (default-locale)
3
+ # OpenLoam::Translatable. The record's own column holds the base (default-locale)
4
4
  # value; a translation row overrides it for a specific locale. Additive:
5
5
  # writing a translation never touches the base column, so the base value is
6
6
  # never lost. Tenant-scoped and audited.
7
- class Translation < Loam::TenantRecord
8
- self.table_name = "loam_translations"
7
+ class Translation < OpenLoam::TenantRecord
8
+ self.table_name = "open_loam_translations"
9
9
 
10
- include Loam::Auditable
10
+ include OpenLoam::Auditable
11
11
 
12
12
  belongs_to :translatable, polymorphic: true
13
13
 
@@ -0,0 +1,39 @@
1
+ module OpenLoam
2
+ # Where one tenant wants its domain events delivered. `event_pattern` uses
3
+ # the same rule as OpenLoam::Events.subscribe — a trailing dot is a domain prefix
4
+ # ("rental."), anything else is an exact event name.
5
+ #
6
+ # The secret signs every delivery (X-OpenLoam-Signature), so a receiver can tell
7
+ # a real call from a forged one.
8
+ class WebhookEndpoint < OpenLoam::TenantRecord
9
+ self.table_name = "open_loam_webhook_endpoints"
10
+
11
+ validates :url, presence: true
12
+ validates :event_pattern, presence: true
13
+
14
+ # The server fetches this URL, so a tenant must not be able to aim it at an
15
+ # address only the server can reach. Rejected at save for a clear error;
16
+ # OpenLoam::WebhookDeliveryJob re-checks at delivery, because DNS moves.
17
+ validate :url_is_reachable_from_outside, if: -> { url.present? }
18
+
19
+ scope :active, -> { where(active: true) }
20
+
21
+ before_validation on: :create do
22
+ self.secret ||= SecureRandom.hex(32)
23
+ end
24
+
25
+ # One matcher, shared with the event bus, so "rental." means the same thing
26
+ # to a subscriber and to a webhook.
27
+ def matches?(event_name)
28
+ OpenLoam::Events.pattern_matches?(event_pattern, event_name)
29
+ end
30
+
31
+ private
32
+
33
+ def url_is_reachable_from_outside
34
+ OpenLoam::OutboundUrl.validate!(url)
35
+ rescue OpenLoam::OutboundUrl::BlockedError => error
36
+ errors.add(:url, error.message)
37
+ end
38
+ end
39
+ end
@@ -2,7 +2,7 @@
2
2
  Uploading happens through the entity's own form (it is an update, and the
3
3
  entity's policy decides who may do it); this partial only lists what is
4
4
  already attached. Note that these links are signed URLs, not tenant-checked
5
- addresses — see Loam::Attachable. %>
5
+ addresses — see OpenLoam::Attachable. %>
6
6
  <h2>Files</h2>
7
7
 
8
8
  <% if record.files.attached? %>
@@ -8,7 +8,7 @@
8
8
  <p style="color:#b00"><%= flash[:alert] %></p>
9
9
  <% end %>
10
10
 
11
- <% record.loam_comments.oldest_first.each do |comment| %>
11
+ <% record.open_loam_comments.oldest_first.each do |comment| %>
12
12
  <p>
13
13
  <strong><%= User.find_by(id: comment.author_id)&.name || "—" %></strong>
14
14
  <em><%= comment.created_at.to_fs(:short) %></em><br>
@@ -3,6 +3,10 @@
3
3
  rendering lives once in the gem instead of being inlined per entity. %>
4
4
  <% mode = local_assigns.fetch(:mode, :edit) %>
5
5
  <% record.class.custom_field_definitions.each do |definition| %>
6
+ <%# Not readable by this role: omit the field entirely. Falling through to the
7
+ read-only branch below would print the value to anyone who merely lacks
8
+ WRITE access, which is a different question. %>
9
+ <% next unless policy.custom_field_readable?(definition.name) %>
6
10
  <% param_name = "#{record.model_name.param_key}[custom_fields][#{definition.name}]" %>
7
11
  <% value = record.custom_field(definition.name) %>
8
12
  <p>
@@ -20,14 +24,14 @@
20
24
  <% when "integer", "decimal" %>
21
25
  <%= number_field_tag param_name, value %>
22
26
  <% when "dictionary" %>
23
- <% options = Loam::Dictionaries.entries(definition.dictionary_key).map { |e| [e.label, e.value] } %>
27
+ <% options = OpenLoam::Dictionaries.entries(definition.dictionary_key).map { |e| [e.label, e.value] } %>
24
28
  <%= select_tag param_name, options_for_select(options, value), include_blank: true, id: param_name %>
25
29
  <% else %>
26
30
  <%= text_field_tag param_name, value %>
27
31
  <% end %>
28
32
  <% else %>
29
33
  <%# A dictionary field shows its entry label, not the raw stored code. %>
30
- <% display = definition.field_type == "dictionary" ? Loam::Dictionaries.label_for(definition.dictionary_key, value) : value %>
34
+ <% display = definition.field_type == "dictionary" ? OpenLoam::Dictionaries.label_for(definition.dictionary_key, value) : value %>
31
35
  <%= definition.name.humanize %>: <%= display %>
32
36
  <% if mode == :edit %><em>(read-only for your role)</em><% end %>
33
37
  <% end %>
@@ -1,19 +1,19 @@
1
1
  require "rails/generators"
2
2
  require "rails/generators/active_record"
3
- require "generators/loam/primary_key_options"
3
+ require "generators/open_loam/primary_key_options"
4
4
 
5
- module Loam
5
+ module OpenLoam
6
6
  module Generators
7
- # `rails g loam:entity Equipment name:string daily_rate:decimal --domain rental`
7
+ # `rails g open_loam:entity Equipment name:string daily_rate:decimal --domain rental`
8
8
  #
9
9
  # THE interface for adding a business entity — for humans and AI agents
10
10
  # alike. One command produces a tenant-scoped, audited, evented model, its
11
11
  # policy, an admin screen, and the isolation tests that prove the
12
- # guardrails hold. No decisions about how tenancy/permissions work: Loam
12
+ # guardrails hold. No decisions about how tenancy/permissions work: OpenLoam
13
13
  # already decided.
14
14
  class EntityGenerator < Rails::Generators::NamedBase
15
15
  include ActiveRecord::Generators::Migration
16
- include Loam::Generators::PrimaryKeyOptions
16
+ include OpenLoam::Generators::PrimaryKeyOptions
17
17
 
18
18
  source_root File.expand_path("templates", __dir__)
19
19
 
@@ -21,7 +21,7 @@ module Loam
21
21
  class_option :domain, type: :string, default: "app",
22
22
  desc: "Event domain prefix (-> domain.entity.created)"
23
23
  class_option :encrypt, type: :array, default: [], banner: "field field",
24
- desc: "Encrypt these fields at rest, per tenant (Loam::Encryptable)"
24
+ desc: "Encrypt these fields at rest, per tenant (OpenLoam::Encryptable)"
25
25
  class_option :encrypt_searchable, type: :array, default: [], banner: "field field",
26
26
  desc: "Encrypt + add a blind index for exact-match lookup"
27
27
 
@@ -59,7 +59,7 @@ module Loam
59
59
  end
60
60
 
61
61
  # `namespace:` makes Rails inject the route INTO the existing
62
- # `namespace :admin do` block (the one loam:install wrote) instead of
62
+ # `namespace :admin do` block (the one open_loam:install wrote) instead of
63
63
  # stacking one admin block per entity, and falls back to creating the
64
64
  # block when there is none. Re-running the generator is a no-op: the
65
65
  # injection is `force: false`, so identical routing code is skipped.
@@ -70,7 +70,7 @@ module Loam
70
70
  # identical `resources :gadgets` lines would leave the second one out.
71
71
  #
72
72
  # The admin resource carries the soft-delete recycle bin: `deleted` lists
73
- # `only_deleted`, `restore` brings one back (see Loam::SoftDeletable).
73
+ # `only_deleted`, `restore` brings one back (see OpenLoam::SoftDeletable).
74
74
  def add_route
75
75
  route <<~RUBY.strip, namespace: :admin
76
76
  resources :#{plural_file_name} do
@@ -101,7 +101,7 @@ module Loam
101
101
  attributes.map(&:name)
102
102
  end
103
103
 
104
- # Only text-ish columns are worth a LIKE (see Loam::Searchable), so an
104
+ # Only text-ish columns are worth a LIKE (see OpenLoam::Searchable), so an
105
105
  # entity with none gets no `searchable_by` declaration and stays out of
106
106
  # the global search. Encrypted columns are excluded: ciphertext is
107
107
  # meaningless to LIKE, and declaring both would raise at class load.
@@ -8,8 +8,9 @@ module Api
8
8
  before_action :set_record, only: %i[show update destroy]
9
9
 
10
10
  def index
11
+ authorize!(policy_for(<%= class_name %>.new), :read?)
11
12
  records = <%= class_name %>.order(created_at: :desc).to_a
12
- enriched = Loam::Enrichers.enrich_many(records) # one batched pass, no N+1
13
+ enriched = OpenLoam::Enrichers.enrich_many(records) # one batched pass, no N+1
13
14
  render json: records.map { |record| entity_json(record, enrichments: enriched[record.id]) }
14
15
  end
15
16