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
@@ -37,3 +37,26 @@ audit trail. Send it as <code>Authorization: Bearer &lt;token&gt;</code>.</p>
37
37
  <%% end %>
38
38
  </tbody>
39
39
  </table>
40
+
41
+ <%% if @tenant_records.any? %>
42
+ <h2>Everyone else's tokens</h2>
43
+ <p>Manager view. Removing someone's membership already stops their tokens
44
+ working; revoking is the clean end of it.</p>
45
+
46
+ <table>
47
+ <thead>
48
+ <tr><th>Owner</th><th>Label</th><th>Created</th><th>Last used</th><th></th></tr>
49
+ </thead>
50
+ <tbody>
51
+ <%% @tenant_records.each do |record| %>
52
+ <tr>
53
+ <td><%%= record.user.name %></td>
54
+ <td><%%= record.label %></td>
55
+ <td><%%= record.created_at.to_fs(:short) %></td>
56
+ <td><%%= record.last_used_at&.to_fs(:short) || "never" %></td>
57
+ <td><%%= button_to "Revoke", admin_api_token_path(record), method: :delete, form: { style: "display:inline" } %></td>
58
+ </tr>
59
+ <%% end %>
60
+ </tbody>
61
+ </table>
62
+ <%% end %>
@@ -4,22 +4,42 @@ module Admin
4
4
 
5
5
  layout "admin"
6
6
 
7
- before_action :set_loam_context
7
+ before_action :set_open_loam_context
8
+ before_action :require_mfa_enrollment!
8
9
  before_action :set_locale
9
10
 
10
- rescue_from Loam::NotAuthorizedError do
11
+ # Fails an action that never authorized anything. Forgetting `authorize!` is
12
+ # otherwise silent — the screen renders, and nothing says the policy was
13
+ # never consulted.
14
+ #
15
+ # It runs AFTER the action, so it catches the omission in development and in
16
+ # tests; it is not a runtime access-control layer. A `destroy` that forgot to
17
+ # authorize has already destroyed the record by the time this raises.
18
+ after_action :verify_authorized!
19
+
20
+ # For screens that are authorized STRUCTURALLY rather than by a policy call —
21
+ # a list scoped to current_actor, a pre-auth screen. The reason is required
22
+ # and shows up in the source, so every exemption is a documented claim rather
23
+ # than a silent opt-out.
24
+ def self.skip_authorization!(reason, **options)
25
+ raise ArgumentError, "skip_authorization! needs a reason" if reason.to_s.strip.empty?
26
+
27
+ skip_after_action :verify_authorized!, **options
28
+ end
29
+
30
+ rescue_from OpenLoam::NotAuthorizedError do
11
31
  render plain: "403 Forbidden — your role does not permit this action.", status: :forbidden
12
32
  end
13
33
 
14
34
  # A disabled feature is "not here" for this tenant, so it 404s — unlike a
15
35
  # policy refusal (403), which is "you may not". Capability vs. person.
16
- rescue_from Loam::FeatureDisabledError do
36
+ rescue_from OpenLoam::FeatureDisabledError do
17
37
  render plain: "404 Not Found — this feature is not enabled for your tenant.", status: :not_found
18
38
  end
19
39
 
20
40
  # A business rule (or any code) can veto a workflow transition from
21
- # `loam_perform_transition!`. Surface it as a clean 422, never a 500.
22
- rescue_from Loam::TransitionVetoedError do |error|
41
+ # `open_loam_perform_transition!`. Surface it as a clean 422, never a 500.
42
+ rescue_from OpenLoam::TransitionVetoedError do |error|
23
43
  render plain: "422 — #{error.message}", status: :unprocessable_entity
24
44
  end
25
45
 
@@ -41,49 +61,65 @@ module Admin
41
61
  end
42
62
 
43
63
  # Locale is request state like the tenant: from the switcher (param),
44
- # remembered in the session, defaulting to Loam.default_locale. Content
45
- # translations (Loam::Translatable) overlay onto it.
64
+ # remembered in the session, defaulting to OpenLoam.default_locale. Content
65
+ # translations (OpenLoam::Translatable) overlay onto it.
46
66
  def set_locale
47
- session[:locale] = params[:locale] if params[:locale].present? && Loam.locales.include?(params[:locale])
48
- Loam::Current.locale = session[:locale] || Loam.default_locale
67
+ session[:locale] = params[:locale] if params[:locale].present? && OpenLoam.locales.include?(params[:locale])
68
+ OpenLoam::Current.locale = session[:locale] || OpenLoam.default_locale
49
69
  # Drive Rails i18n (UI strings) off the same choice, so the switcher moves
50
70
  # both the chrome and the content — but only to a locale that actually has
51
71
  # translations, so an unlisted locale keeps English UI (data still overlays).
52
- chosen = Loam::Current.locale.to_s
72
+ chosen = OpenLoam::Current.locale.to_s
53
73
  I18n.locale = chosen if I18n.available_locales.map(&:to_s).include?(chosen)
54
74
  end
55
75
 
56
- def current_locale = Loam::Current.locale
76
+ def current_locale = OpenLoam::Current.locale
57
77
 
58
- def current_tenant = Loam::Current.tenant
59
- def current_actor = Loam::Current.actor
78
+ def current_tenant = OpenLoam::Current.tenant
79
+ def current_actor = OpenLoam::Current.actor
60
80
 
61
- # Establishes Loam::Current from the session, then proves the actor is
81
+ # Establishes OpenLoam::Current from the session, then proves the actor is
62
82
  # allowed to be here.
63
83
  #
64
- # The order is load-bearing. Loam::Membership is itself tenant-scoped, so
84
+ # The order is load-bearing. OpenLoam::Membership is itself tenant-scoped, so
65
85
  # "is this actor a member?" is a question you can only ask from inside a
66
- # tenant — the tenant has to be in Loam::Current first, or the check raises
67
- # Loam::MissingTenantError instead of answering. The `&&` chain below
86
+ # tenant — the tenant has to be in OpenLoam::Current first, or the check raises
87
+ # OpenLoam::MissingTenantError instead of answering. The `&&` chain below
68
88
  # guarantees that: membership is only consulted once both are set.
69
89
  #
70
90
  # If anything fails, BOTH halves of the context are cleared before the
71
91
  # redirect, so a half-established context can never leak into the next
72
92
  # request or into a job enqueued from it.
73
- def set_loam_context
74
- Loam::Current.tenant = Loam::Tenant.find_by(id: session[:tenant_id])
75
- Loam::Current.actor = User.find_by(id: session[:user_id])
93
+ def set_open_loam_context
94
+ OpenLoam::Current.tenant = OpenLoam::Tenant.find_by(id: session[:tenant_id])
95
+ OpenLoam::Current.actor = User.find_by(id: session[:user_id])
76
96
 
77
97
  return if current_tenant && current_actor && member_of_current_tenant?
78
98
 
79
- Loam::Current.reset
99
+ OpenLoam::Current.reset
80
100
  redirect_to new_admin_session_path
81
101
  end
82
102
 
83
- # Scoped to the current tenant by Loam::TenantRecord, which is the whole
103
+ # Scoped to the current tenant by OpenLoam::TenantRecord, which is the whole
84
104
  # point: a membership in some other tenant is not membership here.
85
105
  def member_of_current_tenant?
86
- Loam::Membership.exists?(user_id: current_actor.id)
106
+ OpenLoam::Membership.exists?(user_id: current_actor.id)
107
+ end
108
+
109
+ # security.mfa_required_roles has to hold on EVERY request, not just as a
110
+ # redirect at the end of login — otherwise typing any other admin URL walks
111
+ # straight past enrollment and the requirement means nothing.
112
+ def require_mfa_enrollment!
113
+ return if OpenLoam::MfaCredential.active_for(current_actor)
114
+ return unless mfa_required_role?
115
+
116
+ redirect_to new_admin_mfa_path,
117
+ alert: "Your role requires two-factor authentication — set it up to continue."
118
+ end
119
+
120
+ def mfa_required_role?
121
+ required = Array(OpenLoam::Configs.get("security.mfa_required_roles", default: []))
122
+ required.map(&:to_s).include?(current_role.to_s)
87
123
  end
88
124
 
89
125
  # Drives the bell in the admin layout. One COUNT per admin page render,
@@ -91,54 +127,77 @@ module Admin
91
127
  def unread_notification_count
92
128
  return 0 unless current_actor
93
129
 
94
- Loam::Notification.unread.where(user_id: current_actor.id).count
130
+ OpenLoam::Notification.unread.where(user_id: current_actor.id).count
95
131
  end
96
132
 
97
133
  # Staged changes awaiting review, for the "Approvals (N)" nav badge.
98
134
  def pending_approval_count
99
135
  return 0 unless current_tenant
100
136
 
101
- Loam::PendingAction.pending.count
137
+ OpenLoam::PendingAction.pending.count
102
138
  end
103
139
 
104
140
  def policy_for(record)
105
- Loam::Policy.for(record)
141
+ OpenLoam::Policy.for(record)
106
142
  end
107
143
 
108
144
  def authorize!(policy, action)
109
- raise Loam::NotAuthorizedError unless policy.public_send(action)
145
+ authorized!
146
+ raise OpenLoam::NotAuthorizedError unless policy.public_send(action)
147
+ end
148
+
149
+ # Records that this action asked a person-authorization question — marked
150
+ # whether the answer was yes or no, since a refusal is still a check.
151
+ #
152
+ # Call it directly only where the check is an inline `raise` rather than one
153
+ # of the helpers below (see Admin::PerspectivesController). Deliberately NOT
154
+ # called by require_feature! (a capability gate, not a person gate) or
155
+ # require_sudo! (step-up, orthogonal to role) — either marking the action
156
+ # would let a feature-gated screen skip authorization silently.
157
+ def authorized!
158
+ @open_loam_authorized = true
159
+ end
160
+
161
+ def verify_authorized!
162
+ return if @open_loam_authorized
163
+
164
+ raise OpenLoam::AuthorizationNotPerformedError,
165
+ "#{self.class.name}##{action_name} finished without authorizing. Call authorize!/require_role!/" \
166
+ "require_permission!, or declare `skip_authorization! \"<why>\"` if it is authorized structurally."
110
167
  end
111
168
 
112
169
  # For admin screens with no per-record policy (e.g. field definitions,
113
170
  # which apply to a whole entity_type rather than one record).
114
171
  def current_role
115
- Loam::Membership.find_by(user_id: current_actor&.id)&.role&.to_sym
172
+ OpenLoam::Membership.find_by(user_id: current_actor&.id)&.role&.to_sym
116
173
  end
117
174
 
118
175
  def require_role!(*roles)
119
- raise Loam::NotAuthorizedError unless roles.include?(current_role)
176
+ authorized!
177
+ raise OpenLoam::NotAuthorizedError unless roles.include?(current_role)
120
178
  end
121
179
 
122
- # Feature-string permission gate (Loam::Permissions) — a finer capability
180
+ # Feature-string permission gate (OpenLoam::Permissions) — a finer capability
123
181
  # check than require_role!. `can?` is a helper_method so views hide UI a role
124
182
  # lacks; require_permission! enforces it (403) server-side.
125
183
  def can?(permission)
126
- Loam.can?(permission)
184
+ OpenLoam.can?(permission)
127
185
  end
128
186
 
129
187
  def require_permission!(permission)
130
- raise Loam::NotAuthorizedError unless Loam.can?(permission)
188
+ authorized!
189
+ raise OpenLoam::NotAuthorizedError unless OpenLoam.can?(permission)
131
190
  end
132
191
 
133
192
  # Feature guards. These gate a CAPABILITY (is the feature on for this
134
193
  # tenant), orthogonal to require_role! / policies, which gate a PERSON.
135
194
  # `feature_on?` is a helper_method, so views can hide UI a flag turns off.
136
195
  def feature_on?(name)
137
- Loam::Features.on?(name)
196
+ OpenLoam::Features.on?(name)
138
197
  end
139
198
 
140
199
  def require_feature!(name)
141
- raise Loam::FeatureDisabledError unless Loam::Features.on?(name)
200
+ raise OpenLoam::FeatureDisabledError unless OpenLoam::Features.on?(name)
142
201
  end
143
202
 
144
203
  # Optimistic-locking conflict handling: someone saved this record between the
@@ -146,13 +205,17 @@ module Admin
146
205
  # StaleObjectError). Build a field-level diff of what the user tried to write
147
206
  # vs the current saved values, then RELOAD to fresh data so the retry carries
148
207
  # the new lock_version — never a 500, never a silent clobber. lock_version is
149
- # the guarantee; the advisory Loam::RecordLock is only a courtesy warning.
208
+ # the guarantee; the advisory OpenLoam::RecordLock is only a courtesy warning.
150
209
  #
151
210
  # Encrypted fields are compared and shown via the reader (decrypted); the raw
152
211
  # column is ciphertext that never matches (random IV), so a byte compare would
153
212
  # be a false conflict every time.
154
213
  def stale_conflict!(record, fields)
155
- encrypted = record.class.respond_to?(:loam_encrypted_attributes) ? record.class.loam_encrypted_attributes.map(&:to_s) : []
214
+ encrypted = record.class.respond_to?(:open_loam_encrypted_attributes) ? record.class.open_loam_encrypted_attributes.map(&:to_s) : []
215
+ # The diff shows the OTHER person's saved values, so it obeys the same field
216
+ # rules as any other read. Anything not writable was never submitted anyway.
217
+ policy = policy_for(record)
218
+ fields = fields.select { |field| policy.writable?(field) && policy.readable?(field) }
156
219
  attempted = fields.map(&:to_s).index_with { |field| conflict_value(record, field, encrypted) }
157
220
 
158
221
  record.reload
@@ -199,12 +262,12 @@ module Admin
199
262
  submitted = Array(params.dig(record.model_name.param_key, :files)).reject(&:blank?)
200
263
  return if submitted.empty?
201
264
 
202
- raise Loam::NotAuthorizedError unless policy.update?
265
+ raise OpenLoam::NotAuthorizedError unless policy.update?
203
266
 
204
267
  record.files.attach(submitted)
205
268
  end
206
269
 
207
- # Runtime custom fields (see Loam::CustomFields) go through the same
270
+ # Runtime custom fields (see OpenLoam::CustomFields) go through the same
208
271
  # field-level enforcement as real columns: only a writable definition's
209
272
  # value is ever assigned.
210
273
  def assign_custom_fields!(record, params, policy)
@@ -1,5 +1,5 @@
1
1
  module Admin
2
- # Managing business rules (Loam::BusinessRule) — manager-only, since a rule can
2
+ # Managing business rules (OpenLoam::BusinessRule) — manager-only, since a rule can
3
3
  # notify, emit events, and set fields across the tenant. The condition and
4
4
  # actions are edited as JSON for the prototype (a visual builder is a future
5
5
  # increment). The recent execution log shows WHY rules fired.
@@ -9,21 +9,21 @@ module Admin
9
9
 
10
10
  rescue_from JSON::ParserError do |error|
11
11
  flash.now[:alert] = "Condition and actions must be valid JSON: #{error.message}"
12
- @rule ||= Loam::BusinessRule.new
12
+ @rule ||= OpenLoam::BusinessRule.new
13
13
  render(@rule.persisted? ? :edit : :new, status: :unprocessable_entity)
14
14
  end
15
15
 
16
16
  def index
17
- @rules = Loam::BusinessRule.by_priority
18
- @recent_runs = Loam::BusinessRuleRun.recent.limit(20)
17
+ @rules = OpenLoam::BusinessRule.by_priority
18
+ @recent_runs = OpenLoam::BusinessRuleRun.recent.limit(20)
19
19
  end
20
20
 
21
21
  def new
22
- @rule = Loam::BusinessRule.new(active: true, condition: {}, actions: [])
22
+ @rule = OpenLoam::BusinessRule.new(active: true, condition: {}, actions: [])
23
23
  end
24
24
 
25
25
  def create
26
- @rule = Loam::BusinessRule.new(rule_params)
26
+ @rule = OpenLoam::BusinessRule.new(rule_params)
27
27
  if @rule.save
28
28
  redirect_to admin_business_rules_path, notice: "Rule created."
29
29
  else
@@ -49,7 +49,7 @@ module Admin
49
49
  private
50
50
 
51
51
  def set_rule
52
- @rule = Loam::BusinessRule.find(params[:id])
52
+ @rule = OpenLoam::BusinessRule.find(params[:id])
53
53
  end
54
54
 
55
55
  # Parse the JSON textareas here; a malformed value raises JSON::ParserError,
@@ -2,8 +2,8 @@
2
2
  <p style="color:#b00"><%%= @error || flash[:alert] %></p>
3
3
  <%% end %>
4
4
 
5
- <%# The model is namespaced (Loam::BusinessRule), so an inferred URL/param-scope
6
- would be admin_loam_business_rule_path / loam_business_rule[...]. Pin both to
5
+ <%# The model is namespaced (OpenLoam::BusinessRule), so an inferred URL/param-scope
6
+ would be admin_open_loam_business_rule_path / open_loam_business_rule[...]. Pin both to
7
7
  the un-namespaced route and the "business_rule" key the controller permits. %>
8
8
  <%%= form_with model: @rule, scope: :business_rule,
9
9
  url: (@rule.persisted? ? admin_business_rule_path(@rule) : admin_business_rules_path) do |f| %>
@@ -14,7 +14,7 @@ module Admin
14
14
  # discussion too.
15
15
  authorize!(policy_for(record), :read?)
16
16
 
17
- comment = record.loam_comments.new(body: params[:body], author: current_actor)
17
+ comment = record.open_loam_comments.new(body: params[:body], author: current_actor)
18
18
  flash[:alert] = comment.errors.full_messages.to_sentence unless comment.save
19
19
 
20
20
  redirect_back fallback_location: [ :admin, record ]
@@ -24,13 +24,13 @@ module Admin
24
24
 
25
25
  # The type arrives from the browser, so it is never constantized blindly.
26
26
  # `safe_constantize` returns nil for anything that is not a real constant,
27
- # and the class then has to prove it is a Loam entity that opted into
27
+ # and the class then has to prove it is a OpenLoam entity that opted into
28
28
  # comments — the allowlist is the contract, not a hand-kept list of names.
29
29
  def commentable
30
30
  klass = params[:commentable_type].to_s.safe_constantize
31
31
 
32
- unless klass.is_a?(Class) && klass < Loam::TenantRecord && klass.include?(Loam::Commentable)
33
- raise Loam::NotAuthorizedError, "#{params[:commentable_type].inspect} is not a commentable Loam entity"
32
+ unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord && klass.include?(OpenLoam::Commentable)
33
+ raise OpenLoam::NotAuthorizedError, "#{params[:commentable_type].inspect} is not a commentable OpenLoam entity"
34
34
  end
35
35
 
36
36
  klass.find(params[:commentable_id])
@@ -1,33 +1,33 @@
1
1
  module Admin
2
- # Per-tenant settings (Loam::Configs). Structural, not per-record, so it is
2
+ # Per-tenant settings (OpenLoam::Configs). Structural, not per-record, so it is
3
3
  # gated on the manager role rather than an entity policy — settings are
4
- # privileged. Everything routes through Loam::Configs, never Loam::Config
4
+ # privileged. Everything routes through OpenLoam::Configs, never OpenLoam::Config
5
5
  # directly, so the cross-level resolution stays in vetted gem code.
6
6
  class ConfigsController < BaseController
7
7
  before_action { require_role!(:manager) }
8
8
 
9
9
  def index
10
- # Feature flags share the loam_configs store but have their own screen
10
+ # Feature flags share the open_loam_configs store but have their own screen
11
11
  # (/admin/features), so hide their reserved-prefix keys from Settings.
12
- keys = Loam::Configs.defined_keys.reject { |key| key.start_with?(Loam::Features::PREFIX) }
12
+ keys = OpenLoam::Configs.defined_keys.reject { |key| key.start_with?(OpenLoam::Features::PREFIX) }
13
13
  @settings = keys.map do |key|
14
- { key: key, value: Loam::Configs.get(key), overridden: Loam::Configs.overridden?(key) }
14
+ { key: key, value: OpenLoam::Configs.get(key), overridden: OpenLoam::Configs.overridden?(key) }
15
15
  end
16
16
  end
17
17
 
18
18
  def edit
19
19
  @key = params[:key]
20
- @value = Loam::Configs.get(@key)
21
- @overridden = Loam::Configs.overridden?(@key)
20
+ @value = OpenLoam::Configs.get(@key)
21
+ @overridden = OpenLoam::Configs.overridden?(@key)
22
22
  end
23
23
 
24
24
  def update
25
- Loam::Configs.set(params[:key], coerce(params[:value]))
25
+ OpenLoam::Configs.set(params[:key], coerce(params[:value]))
26
26
  redirect_to admin_configs_path, notice: "#{params[:key]} set for this tenant."
27
27
  end
28
28
 
29
29
  def reset
30
- Loam::Configs.reset(params[:key])
30
+ OpenLoam::Configs.reset(params[:key])
31
31
  redirect_to admin_configs_path, notice: "#{params[:key]} reset to the default."
32
32
  end
33
33
 
@@ -0,0 +1,12 @@
1
+ module Admin
2
+ class DashboardController < BaseController
3
+ skip_authorization! "Widgets are filtered by role in OpenLoam::Dashboard.for; there is no per-record subject."
4
+
5
+ def index
6
+ # Configurable, role-visible widgets (OpenLoam::Dashboard) — a raising widget
7
+ # is isolated into an error tile, never breaking the page. Register your
8
+ # own with OpenLoam::Widgets.register; arrange them under Dashboard settings.
9
+ @widgets = OpenLoam::Dashboard.for(actor: current_actor, role: current_role)
10
+ end
11
+ end
12
+ end
@@ -1,18 +1,18 @@
1
1
  module Admin
2
- # Dashboard settings (Loam::DashboardWidget) — manager-only. Choose which
2
+ # Dashboard settings (OpenLoam::DashboardWidget) — manager-only. Choose which
3
3
  # registered widgets appear and in what order for this tenant.
4
4
  class DashboardWidgetsController < BaseController
5
5
  before_action { require_role!(:manager) }
6
6
 
7
7
  def index
8
- @registered = Loam::Widgets.registered
9
- @config = Loam::DashboardWidget.all.index_by(&:widget_key)
8
+ @registered = OpenLoam::Widgets.registered
9
+ @config = OpenLoam::DashboardWidget.all.index_by(&:widget_key)
10
10
  end
11
11
 
12
12
  def update
13
- Loam::Widgets.keys.each do |key|
13
+ OpenLoam::Widgets.keys.each do |key|
14
14
  attrs = params.dig(:widgets, key) || {}
15
- row = Loam::DashboardWidget.find_or_initialize_by(widget_key: key)
15
+ row = OpenLoam::DashboardWidget.find_or_initialize_by(widget_key: key)
16
16
  row.active = attrs[:active] == "1"
17
17
  row.position = attrs[:position].to_i
18
18
  row.save!
@@ -1,5 +1,5 @@
1
1
  module Admin
2
- # Managed lookup lists (Loam::Dictionary) — manager-only. The index lists a
2
+ # Managed lookup lists (OpenLoam::Dictionary) — manager-only. The index lists a
3
3
  # tenant's dictionaries; the edit screen curates a dictionary's entries
4
4
  # (Admin::DictionaryEntriesController). Tenant-scoped by the model's default
5
5
  # scope, so a manager only ever sees their own tenant's lists.
@@ -8,15 +8,15 @@ module Admin
8
8
  before_action :set_dictionary, only: %i[edit update destroy]
9
9
 
10
10
  def index
11
- @dictionaries = Loam::Dictionary.order(:key)
11
+ @dictionaries = OpenLoam::Dictionary.order(:key)
12
12
  end
13
13
 
14
14
  def new
15
- @dictionary = Loam::Dictionary.new
15
+ @dictionary = OpenLoam::Dictionary.new
16
16
  end
17
17
 
18
18
  def create
19
- @dictionary = Loam::Dictionary.new(dictionary_params)
19
+ @dictionary = OpenLoam::Dictionary.new(dictionary_params)
20
20
  if @dictionary.save
21
21
  redirect_to edit_admin_dictionary_path(@dictionary), notice: "Dictionary created — add its entries below."
22
22
  else
@@ -45,7 +45,7 @@ module Admin
45
45
  private
46
46
 
47
47
  def set_dictionary
48
- @dictionary = Loam::Dictionary.find(params[:id])
48
+ @dictionary = OpenLoam::Dictionary.find(params[:id])
49
49
  end
50
50
 
51
51
  def dictionary_params
@@ -1,4 +1,4 @@
1
- <%%# Loam::Dictionary is namespaced, so pin the URL + param scope. %>
1
+ <%%# OpenLoam::Dictionary is namespaced, so pin the URL + param scope. %>
2
2
  <%%= form_with model: @dictionary, scope: :dictionary,
3
3
  url: (@dictionary.persisted? ? admin_dictionary_path(@dictionary) : admin_dictionaries_path) do |f| %>
4
4
  <%% if @dictionary.errors.any? %>
@@ -1,5 +1,5 @@
1
1
  module Admin
2
- # Entries of a Loam::Dictionary — manager-only, nested under a dictionary.
2
+ # Entries of a OpenLoam::Dictionary — manager-only, nested under a dictionary.
3
3
  # Add / edit (value, label, color, icon, position, default, active) / delete;
4
4
  # reordering is by editing `position`.
5
5
  class DictionaryEntriesController < BaseController
@@ -33,7 +33,7 @@ module Admin
33
33
  private
34
34
 
35
35
  def set_dictionary
36
- @dictionary = Loam::Dictionary.find(params[:dictionary_id])
36
+ @dictionary = OpenLoam::Dictionary.find(params[:dictionary_id])
37
37
  end
38
38
 
39
39
  def set_entry
@@ -1,5 +1,5 @@
1
1
  module Admin
2
- # Durable event deliveries (Loam::EventDelivery) — the dead-letter view.
2
+ # Durable event deliveries (OpenLoam::EventDelivery) — the dead-letter view.
3
3
  # Manager-only. Lists deliveries parked as `dead` (handler removed, or retries
4
4
  # exhausted) and lets an operator requeue one after fixing the handler. Pending
5
5
  # deliveries drain on their own; a small sample is shown for visibility.
@@ -7,16 +7,16 @@ module Admin
7
7
  before_action { require_role!(:manager) }
8
8
 
9
9
  def index
10
- @dead = Loam::EventDelivery.dead.order(updated_at: :desc).limit(200)
11
- @pending = Loam::EventDelivery.pending.order(Arel.sql("next_attempt_at IS NULL DESC, next_attempt_at ASC")).limit(50)
10
+ @dead = OpenLoam::EventDelivery.dead.order(updated_at: :desc).limit(200)
11
+ @pending = OpenLoam::EventDelivery.pending.order(Arel.sql("next_attempt_at IS NULL DESC, next_attempt_at ASC")).limit(50)
12
12
  end
13
13
 
14
14
  # Re-arm one delivery: back to pending with a clean slate, and nudge a job.
15
15
  # The sweep would eventually pick it up anyway; this is the "try now" button.
16
16
  def redeliver
17
- delivery = Loam::EventDelivery.find(params[:id])
17
+ delivery = OpenLoam::EventDelivery.find(params[:id])
18
18
  delivery.update!(status: "pending", attempts: 0, next_attempt_at: nil, last_error: nil)
19
- Loam::EventDeliveryJob.perform_later(delivery.tenant_id, delivery.id)
19
+ OpenLoam::EventDeliveryJob.perform_later(delivery.tenant_id, delivery.id)
20
20
  redirect_to admin_event_deliveries_path, notice: "Delivery requeued."
21
21
  end
22
22
  end
@@ -1,6 +1,6 @@
1
1
  <h1>Event deliveries</h1>
2
2
 
3
- <p>Durable subscribers (<code>Loam::DurableEvents</code>). Delivery is at-least-once
3
+ <p>Durable subscribers (<code>OpenLoam::DurableEvents</code>). Delivery is at-least-once
4
4
  with retry; a delivery that exhausts its retries — or whose subscriber was removed —
5
5
  is parked here as <strong>dead</strong> for you to fix and requeue.</p>
6
6
 
@@ -1,12 +1,14 @@
1
1
  module Admin
2
- # Server-Sent Events: a per-tenant push stream (Loam::EventStream) so the admin
2
+ # Server-Sent Events: a per-tenant push stream (OpenLoam::EventStream) so the admin
3
3
  # updates live instead of polling. Authentication runs in the normal
4
- # before_action chain (set_loam_context) BEFORE any stream write — so an
4
+ # before_action chain (set_open_loam_context) BEFORE any stream write — so an
5
5
  # unauthenticated request redirects to login the ordinary way, since no headers
6
6
  # have been committed yet. Once the loop starts writing, the response is
7
7
  # committed and there is no going back; that ordering is the whole safety of a
8
8
  # Live action.
9
9
  class EventsController < BaseController
10
+ skip_authorization! "OpenLoam::EventStream gates every message: declared pattern, matching tenant, and audience."
11
+
10
12
  include ActionController::Live
11
13
 
12
14
  HEARTBEAT_SECONDS = 20
@@ -20,7 +22,7 @@ module Admin
20
22
  # Subscribe as the FIRST thing in the begin whose ensure unsubscribes: a
21
23
  # leaked AS::Notifications subscriber would fire on every future event in
22
24
  # the process, holding this dead stream's queue forever.
23
- handle = Loam::EventStream.broadcaster.subscribe(tenant: current_tenant, actor: current_actor) do |sse|
25
+ handle = OpenLoam::EventStream.broadcaster.subscribe(tenant: current_tenant, actor: current_actor) do |sse|
24
26
  queue << sse
25
27
  end
26
28
 
@@ -34,9 +36,9 @@ module Admin
34
36
  rescue IOError, ActionController::Live::ClientDisconnected
35
37
  # The client went away — nothing to do but clean up in `ensure`.
36
38
  ensure
37
- Loam::EventStream.broadcaster.unsubscribe(handle)
39
+ OpenLoam::EventStream.broadcaster.unsubscribe(handle)
38
40
  response.stream.close
39
- Loam::Current.reset
41
+ OpenLoam::Current.reset
40
42
  end
41
43
  end
42
44
  end
@@ -1,34 +1,34 @@
1
1
  module Admin
2
- # Feature flags (Loam::Features). Structural and privileged, so manager-only,
3
- # like Settings. Everything routes through Loam::Features; the admin toggles
2
+ # Feature flags (OpenLoam::Features). Structural and privileged, so manager-only,
3
+ # like Settings. Everything routes through OpenLoam::Features; the admin toggles
4
4
  # the CURRENT tenant's override, and Reset drops it back to the declared /
5
5
  # global state. Global (app-wide) flips are a deploy concern, not this screen.
6
6
  class FeaturesController < BaseController
7
7
  before_action { require_role!(:manager) }
8
8
 
9
9
  def index
10
- @flags = Loam::Features.declared.map do |name|
10
+ @flags = OpenLoam::Features.declared.map do |name|
11
11
  {
12
12
  name: name,
13
- on: Loam::Features.on?(name),
14
- overridden: Loam::Features.overridden?(name),
15
- description: Loam::Features.description(name)
13
+ on: OpenLoam::Features.on?(name),
14
+ overridden: OpenLoam::Features.overridden?(name),
15
+ description: OpenLoam::Features.description(name)
16
16
  }
17
17
  end
18
18
  end
19
19
 
20
20
  def enable
21
- Loam::Features.enable(params[:name])
21
+ OpenLoam::Features.enable(params[:name])
22
22
  redirect_to admin_features_path, notice: "#{params[:name]} enabled for this tenant."
23
23
  end
24
24
 
25
25
  def disable
26
- Loam::Features.disable(params[:name])
26
+ OpenLoam::Features.disable(params[:name])
27
27
  redirect_to admin_features_path, notice: "#{params[:name]} disabled for this tenant."
28
28
  end
29
29
 
30
30
  def reset
31
- Loam::Features.reset(params[:name])
31
+ OpenLoam::Features.reset(params[:name])
32
32
  redirect_to admin_features_path, notice: "#{params[:name]} reset to the default."
33
33
  end
34
34
  end