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,5 +1,5 @@
1
1
  module Admin
2
- # Manages Loam::FieldDefinition records: the "migration-free field" screen.
2
+ # Manages OpenLoam::FieldDefinition records: the "migration-free field" screen.
3
3
  # Structural, not per-entity — gated on the manager role rather than a
4
4
  # generated entity policy, since a field definition applies to a whole
5
5
  # entity_type, not one record.
@@ -9,16 +9,16 @@ module Admin
9
9
 
10
10
  def index
11
11
  @entity_type = params[:entity_type]
12
- @records = Loam::FieldDefinition.order(:entity_type, :name)
12
+ @records = OpenLoam::FieldDefinition.order(:entity_type, :name)
13
13
  @records = @records.where(entity_type: @entity_type) if @entity_type.present?
14
14
  end
15
15
 
16
16
  def new
17
- @record = Loam::FieldDefinition.new(entity_type: params[:entity_type])
17
+ @record = OpenLoam::FieldDefinition.new(entity_type: params[:entity_type])
18
18
  end
19
19
 
20
20
  def create
21
- @record = Loam::FieldDefinition.new(permitted_params)
21
+ @record = OpenLoam::FieldDefinition.new(permitted_params)
22
22
  @record.writable_roles = parse_roles(params.dig(:field_definition, :writable_roles))
23
23
  @record.readable_roles = parse_roles(params.dig(:field_definition, :readable_roles))
24
24
 
@@ -38,7 +38,7 @@ module Admin
38
38
  private
39
39
 
40
40
  def set_record
41
- @record = Loam::FieldDefinition.find(params[:id])
41
+ @record = OpenLoam::FieldDefinition.find(params[:id])
42
42
  end
43
43
 
44
44
  def permitted_params
@@ -19,11 +19,11 @@
19
19
  </p>
20
20
  <p>
21
21
  <%%= f.label :field_type %><br>
22
- <%%= f.select :field_type, Loam::FieldDefinition::FIELD_TYPES %>
22
+ <%%= f.select :field_type, OpenLoam::FieldDefinition::FIELD_TYPES %>
23
23
  </p>
24
24
  <p>
25
25
  <%%= f.label :dictionary_key, "Dictionary key (only for a 'dictionary' field type)" %><br>
26
- <%%= f.select :dictionary_key, Loam::Dictionary.order(:key).pluck(:key), include_blank: true %>
26
+ <%%= f.select :dictionary_key, OpenLoam::Dictionary.order(:key).pluck(:key), include_blank: true %>
27
27
  </p>
28
28
  <p>
29
29
  <%%= f.label :writable_roles, "Writable by (comma-separated roles, blank = any member)" %><br>
@@ -0,0 +1,64 @@
1
+ module Admin
2
+ # A record's change history (OpenLoam::AuditRecord) with one-click Undo (L-704).
3
+ # Undo applies the inverse of a change and records ITSELF as an audit, so
4
+ # undoing an "undo" entry is redo — no separate redo action. Reading needs read
5
+ # on the record; undoing needs update (the same gate as editing it).
6
+ class HistoryController < BaseController
7
+ before_action :set_target
8
+
9
+ helper_method :visible_changeset
10
+
11
+ def index
12
+ authorize!(policy_for(@record), :read?)
13
+ @audits = OpenLoam::AuditRecord
14
+ .where(auditable_type: @record.class.name, auditable_id: @record.id)
15
+ .order(id: :desc).limit(100)
16
+ end
17
+
18
+ def undo
19
+ authorize!(policy_for(@record), :update?)
20
+ audit = OpenLoam::AuditRecord.find(params[:id])
21
+ OpenLoam::Undo.undo(audit, policy: policy_for(@record))
22
+ redirect_to admin_history_path(type: @record.class.name, record_id: @record.id), notice: "Change undone."
23
+ rescue OpenLoam::Undo::NotUndoableError => error
24
+ redirect_to admin_history_path(type: @record.class.name, record_id: @record.id), alert: error.message
25
+ end
26
+
27
+ private
28
+
29
+ # History is a read path like any other: a field the role may not read must
30
+ # not come back here as an old → new pair.
31
+ def visible_changeset(audit)
32
+ policy = (@history_policy ||= policy_for(@record))
33
+
34
+ (audit.changeset || {}).each_with_object({}) do |(field, values), visible|
35
+ if field == "custom_fields"
36
+ readable = readable_custom_fields(values, policy)
37
+ visible[field] = readable if readable
38
+ elsif policy.readable?(field)
39
+ visible[field] = values
40
+ end
41
+ end
42
+ end
43
+
44
+ # Runtime fields are audited as one json blob, so the filter goes inside the
45
+ # old/new pair rather than dropping the whole entry.
46
+ def readable_custom_fields(values, policy)
47
+ return values unless values.is_a?(Array)
48
+
49
+ sides = values.map { |side| side.to_h.select { |name, _| policy.custom_field_readable?(name) } }
50
+ sides.any?(&:present?) ? sides : nil
51
+ end
52
+
53
+ # The target record, looked up through its OWN default scope (tenancy holds;
54
+ # a soft-deleted row is still reachable to restore). The type is whitelisted
55
+ # to a OpenLoam::TenantRecord — never a bare constantize of a param.
56
+ def set_target
57
+ klass = params[:type].to_s.safe_constantize
58
+ raise OpenLoam::NotAuthorizedError unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord
59
+
60
+ scope = klass.respond_to?(:with_deleted) ? klass.with_deleted : klass.all
61
+ @record = scope.find(params[:record_id])
62
+ end
63
+ end
64
+ end
@@ -18,13 +18,13 @@ never reverted here.</p>
18
18
  <td><code><%%= audit.action %></code></td>
19
19
  <td><%%= audit.actor&.name || "—" %></td>
20
20
  <td>
21
- <%% (audit.changeset || {}).each do |field, values| %>
21
+ <%% visible_changeset(audit).each do |field, values| %>
22
22
  <div><code><%%= field %></code>:
23
23
  <%%= values.is_a?(Array) ? "#{values.first.inspect} → #{values.last.inspect}" : values %></div>
24
24
  <%% end %>
25
25
  </td>
26
26
  <td>
27
- <%% if Loam::Undo.undoable?(audit) %>
27
+ <%% if OpenLoam::Undo.undoable?(audit) %>
28
28
  <%%= button_to "Undo", admin_undo_history_path(id: audit.id, type: @record.class.name, record_id: @record.id),
29
29
  form: { style: "display:inline" } %>
30
30
  <%% end %>
@@ -2,7 +2,7 @@ module Admin
2
2
  # Generic CSV importer, scoped by entity_type — manager-only (a privileged bulk
3
3
  # mutation). Flow: new (upload) → preview (map columns) → create (dry-run
4
4
  # summary, or a backgrounded run reporting live progress). The mapping only
5
- # offers fields the manager may write (Loam::Import.allowed_targets); a crafted
5
+ # offers fields the manager may write (OpenLoam::Import.allowed_targets); a crafted
6
6
  # target is refused by the engine.
7
7
  class ImportsController < BaseController
8
8
  before_action { require_role!(:manager) }
@@ -15,11 +15,11 @@ module Admin
15
15
  # Parse the uploaded file, show the mapping form + a few sample rows.
16
16
  def preview
17
17
  @csv = read_upload
18
- preview = Loam::Import.preview(@csv)
18
+ preview = OpenLoam::Import.preview(@csv)
19
19
  @headers = preview[:headers]
20
20
  @sample = preview[:rows]
21
- @targets = Loam::Import.allowed_targets(@model, current_actor).to_a.sort
22
- rescue Loam::Error => error
21
+ @targets = OpenLoam::Import.allowed_targets(@model, current_actor).to_a.sort
22
+ rescue OpenLoam::Error => error
23
23
  redirect_to new_admin_import_path(entity_type: @entity_type), alert: error.message
24
24
  end
25
25
 
@@ -31,23 +31,23 @@ module Admin
31
31
  if params[:commit] == "Dry run"
32
32
  run_dry(mapping, match_key)
33
33
  else
34
- ImportJob.perform_later(entity_type: @entity_type, csv: @csv, mapping: mapping,
34
+ ImportJob.perform_later(entity_type: @entity_type, blob_id: staged_csv_blob(@csv).id, mapping: mapping,
35
35
  match_key: match_key, tenant_id: current_tenant.id, actor_id: current_actor.id)
36
36
  redirect_to admin_progress_jobs_path, notice: "Import started — watch it progress under Tasks."
37
37
  end
38
- rescue Loam::Error => error
38
+ rescue OpenLoam::Error => error
39
39
  redirect_to new_admin_import_path(entity_type: @entity_type), alert: error.message
40
40
  end
41
41
 
42
42
  # The failed rows as a fix-and-re-upload CSV.
43
43
  def download_errors
44
44
  @entity_type = params[:entity_type]
45
- model = Loam::Import.allowed_model(@entity_type)
45
+ model = OpenLoam::Import.allowed_model(@entity_type)
46
46
  csv = params[:csv].to_s
47
47
  mapping = params[:mapping].to_unsafe_h.reject { |_h, target| target.blank? }
48
- result = Loam::Import.run(csv, model: model, mapping: mapping, actor: current_actor,
48
+ result = OpenLoam::Import.run(csv, model: model, mapping: mapping, actor: current_actor,
49
49
  match_key: params[:match_key].presence, dry_run: true)
50
- send_data Loam::Import.error_csv(result, csv),
50
+ send_data OpenLoam::Import.error_csv(result, csv),
51
51
  filename: "#{@entity_type.underscore}-import-errors.csv", type: "text/csv"
52
52
  end
53
53
 
@@ -55,21 +55,33 @@ module Admin
55
55
 
56
56
  def set_model
57
57
  @entity_type = params[:entity_type]
58
- @model = Loam::Import.allowed_model(@entity_type)
58
+ @model = OpenLoam::Import.allowed_model(@entity_type)
59
59
  end
60
60
 
61
61
  def run_dry(mapping, match_key)
62
- @result = Loam::Import.run(@csv, model: @model, mapping: mapping, actor: current_actor,
62
+ @result = OpenLoam::Import.run(@csv, model: @model, mapping: mapping, actor: current_actor,
63
63
  match_key: match_key, dry_run: true)
64
- @headers = Loam::Import.preview(@csv)[:headers]
64
+ @headers = OpenLoam::Import.preview(@csv)[:headers]
65
65
  render :summary
66
66
  end
67
67
 
68
68
  def read_upload
69
69
  file = params[:file]
70
- raise Loam::Error, "choose a CSV file to upload" if file.blank?
70
+ raise OpenLoam::Error, "choose a CSV file to upload" if file.blank?
71
71
 
72
72
  file.read.force_encoding("UTF-8")
73
73
  end
74
+
75
+ # The CSV goes to blob storage and the JOB gets an id. ActiveJob arguments
76
+ # are serialized into the queue backend and echoed in the job log, so
77
+ # passing the file itself would leave every row sitting in the clear —
78
+ # including the columns headed for encrypted fields. ImportJob purges it.
79
+ def staged_csv_blob(csv)
80
+ ActiveStorage::Blob.create_and_upload!(
81
+ io: StringIO.new(csv),
82
+ filename: "#{@entity_type.underscore}-import-#{SecureRandom.hex(8)}.csv",
83
+ content_type: "text/csv"
84
+ )
85
+ end
74
86
  end
75
87
  end
@@ -1,6 +1,6 @@
1
1
  module Admin
2
2
  # Manage the external systems allowed to POST webhooks into this tenant
3
- # (Loam::InboundWebhookSource) — manager-only. Create a source (token + secret
3
+ # (OpenLoam::InboundWebhookSource) — manager-only. Create a source (token + secret
4
4
  # are generated), copy the receive URL and secret to the sender, rotate either,
5
5
  # toggle active, and see the most recent received deliveries.
6
6
  class InboundWebhookSourcesController < BaseController
@@ -8,16 +8,16 @@ module Admin
8
8
  before_action :set_source, only: %i[destroy rotate_secret rotate_token toggle]
9
9
 
10
10
  def index
11
- @sources = Loam::InboundWebhookSource.order(:name)
12
- @recent = Loam::InboundWebhookDelivery.order(received_at: :desc).limit(20)
11
+ @sources = OpenLoam::InboundWebhookSource.order(:name)
12
+ @recent = OpenLoam::InboundWebhookDelivery.order(received_at: :desc).limit(20)
13
13
  end
14
14
 
15
15
  def new
16
- @source = Loam::InboundWebhookSource.new(event_name: "inbound.webhook.received")
16
+ @source = OpenLoam::InboundWebhookSource.new(event_name: "inbound.webhook.received")
17
17
  end
18
18
 
19
19
  def create
20
- @source = Loam::InboundWebhookSource.new(source_params)
20
+ @source = OpenLoam::InboundWebhookSource.new(source_params)
21
21
  if @source.save
22
22
  redirect_to admin_inbound_webhook_sources_path, notice: "Source created. Copy the secret now — it signs every call."
23
23
  else
@@ -48,12 +48,12 @@ module Admin
48
48
  private
49
49
 
50
50
  def set_source
51
- @source = Loam::InboundWebhookSource.find(params[:id])
51
+ @source = OpenLoam::InboundWebhookSource.find(params[:id])
52
52
  end
53
53
 
54
54
  def source_params
55
55
  params.require(:inbound_webhook_source).permit(
56
- :name, :event_name, :signature_header, :delivery_id_header,
56
+ :name, :event_name, :signature_header,
57
57
  :timestamp_header, :timestamp_tolerance
58
58
  )
59
59
  end
@@ -14,13 +14,11 @@
14
14
  <%%= f.text_field :event_name %>
15
15
  </p>
16
16
  <p>
17
- <%%= f.label :signature_header, "Signature header (default X-Loam-Signature)" %><br>
18
- <%%= f.text_field :signature_header, placeholder: "X-Loam-Signature" %>
17
+ <%%= f.label :signature_header, "Signature header (default X-OpenLoam-Signature)" %><br>
18
+ <%%= f.text_field :signature_header, placeholder: "X-OpenLoam-Signature" %>
19
19
  <small>e.g. X-Hub-Signature-256 for GitHub.</small>
20
20
  </p>
21
21
  <p>
22
- <%%= f.label :delivery_id_header, "Delivery-id header (optional, for dedupe)" %><br>
23
- <%%= f.text_field :delivery_id_header %>
24
22
  <small>Blank = dedupe on a hash of the body.</small>
25
23
  </p>
26
24
  <p>
@@ -0,0 +1,76 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Admin</title>
5
+ <%%= csrf_meta_tags %>
6
+ <%%= stylesheet_link_tag "admin", media: "all" %>
7
+ </head>
8
+ <body>
9
+ <%% flash.each do |type, message| %>
10
+ <div class="flash flash-<%%= type.to_s == "alert" ? "alert" : "notice" %>"><%%= message %></div>
11
+ <%% end %>
12
+ <%% if defined?(current_tenant) && current_tenant %>
13
+ <div class="context">
14
+ <%%= t("open_loam.chrome.tenant") %>: <strong><%%= current_tenant.name %></strong> ·
15
+ <%%= t("open_loam.chrome.user") %>: <strong><%%= current_actor&.name %></strong> ·
16
+ <%%= button_to t("open_loam.chrome.sign_out"), admin_session_path, method: :delete, form: { style: "display:inline" } %>
17
+ </div>
18
+ <nav>
19
+ <%%= link_to t("open_loam.nav.dashboard"), admin_root_path %>
20
+ <%%= link_to admin_notifications_path do %><%%= t("open_loam.nav.notifications") %> (<span id="open-loam-notification-count"><%%= unread_notification_count %></span>)<%% end %>
21
+ <%%= link_to t("open_loam.nav.api_tokens"), admin_api_tokens_path %>
22
+ <%%= link_to t("open_loam.nav.security"), admin_mfa_path %>
23
+ <%%= link_to "#{t('open_loam.nav.approvals')} (#{pending_approval_count})", admin_pending_actions_path %>
24
+ <%%= link_to t("open_loam.nav.settings"), admin_configs_path %>
25
+ <%%= link_to t("open_loam.nav.features"), admin_features_path %>
26
+ <%%= link_to t("open_loam.nav.rules"), admin_business_rules_path %>
27
+ <%%= link_to t("open_loam.nav.sso"), admin_sso_providers_path %>
28
+ <%%= link_to t("open_loam.nav.dictionaries"), admin_dictionaries_path %>
29
+ <%%= link_to t("open_loam.nav.tasks"), admin_progress_jobs_path %>
30
+ <%%= link_to t("open_loam.nav.schedules"), admin_scheduled_jobs_path %>
31
+ <%%= link_to t("open_loam.nav.deliveries"), admin_event_deliveries_path %>
32
+ <%%= link_to t("open_loam.nav.inbound"), admin_inbound_webhook_sources_path %>
33
+ <%%= link_to t("open_loam.nav.api_docs"), admin_api_docs_path %>
34
+ <%%= link_to t("open_loam.nav.overrides"), admin_overrides_path %>
35
+ <%%= form_with url: admin_search_path, method: :get, class: "search" do %>
36
+ <%%= text_field_tag :q, params[:q], placeholder: t("open_loam.chrome.search") %>
37
+ <%% end %>
38
+ <%%# Locale switcher (OpenLoam::Translatable): overlays content onto the picked language. %>
39
+ <%% if OpenLoam.locales.size > 1 %>
40
+ <span class="locale">
41
+ <%% OpenLoam.locales.each do |loc| %><%%= link_to loc.upcase, "#{request.path}?locale=#{loc}", style: (loc == current_locale ? "font-weight:bold" : "") %> <%% end %>
42
+ </span>
43
+ <%% end %>
44
+ </nav>
45
+
46
+ <%%# Live updates over SSE (OpenLoam::EventStream). Degrades gracefully: if
47
+ EventSource is unavailable or the stream drops, the per-page-load bell
48
+ count above still works. %>
49
+ <script>
50
+ (function () {
51
+ if (typeof EventSource === "undefined") return;
52
+ var source = new EventSource("<%%= admin_events_stream_path %>");
53
+ source.addEventListener("open_loam.notification.created", function () {
54
+ var badge = document.getElementById("open-loam-notification-count");
55
+ if (badge) badge.textContent = (parseInt(badge.textContent, 10) || 0) + 1;
56
+ });
57
+ // Live task progress (OpenLoam::ProgressJob): move the matching bar. The
58
+ // frame carries only id/percent/status.
59
+ source.addEventListener("open_loam.progress.updated", function (e) {
60
+ var d = JSON.parse(e.data);
61
+ var fill = document.getElementById("progress-fill-" + d.id);
62
+ if (fill) fill.style.width = d.percent + "%";
63
+ var pct = document.getElementById("progress-percent-" + d.id);
64
+ if (pct) pct.textContent = d.percent + "%";
65
+ var st = document.getElementById("progress-status-" + d.id);
66
+ if (st) st.textContent = d.status;
67
+ });
68
+ source.onerror = function () {
69
+ if (source.readyState === EventSource.CLOSED) source.close();
70
+ };
71
+ })();
72
+ </script>
73
+ <%% end %>
74
+ <%%= yield %>
75
+ </body>
76
+ </html>
@@ -6,6 +6,11 @@ module Admin
6
6
  # old secret stays valid until the new one is proven — so neither a cross-site
7
7
  # GET nor an abandoned re-enrollment can downgrade an active credential.
8
8
  class MfaController < BaseController
9
+ skip_authorization! "Your own MFA credentials; sensitive changes go through require_sudo! instead."
10
+
11
+ # This IS the enrollment screen the requirement sends people to.
12
+ skip_before_action :require_mfa_enrollment!
13
+
9
14
  # Shown in authenticator apps as the account issuer; defaults to the app name.
10
15
  ISSUER = Rails.application.class.module_parent_name.freeze
11
16
 
@@ -15,7 +20,7 @@ module Admin
15
20
  before_action :require_sudo!, only: %i[create destroy], if: :active_mfa?
16
21
 
17
22
  def show
18
- @credential = Loam::MfaCredential.active_for(current_actor)
23
+ @credential = OpenLoam::MfaCredential.active_for(current_actor)
19
24
  end
20
25
 
21
26
  # READ-ONLY: mint a candidate secret into the (encrypted cookie) session and
@@ -28,7 +33,7 @@ module Admin
28
33
  # recovery codes once. The active secret is replaced only on success.
29
34
  def create
30
35
  secret = session[:mfa_enrollment_secret]
31
- credential = Loam::MfaCredential.find_or_initialize_by(user_id: current_actor.id)
36
+ credential = OpenLoam::MfaCredential.find_or_initialize_by(user_id: current_actor.id)
32
37
  @recovery_codes = secret && credential.activate_with!(secret, params[:code])
33
38
 
34
39
  if @recovery_codes
@@ -42,7 +47,7 @@ module Admin
42
47
  end
43
48
 
44
49
  def destroy
45
- Loam::MfaCredential.where(user_id: current_actor.id).delete_all
50
+ OpenLoam::MfaCredential.where(user_id: current_actor.id).delete_all
46
51
  session.delete(:mfa_enrollment_secret)
47
52
  redirect_to admin_mfa_path, notice: "Two-factor authentication disabled."
48
53
  end
@@ -50,12 +55,12 @@ module Admin
50
55
  private
51
56
 
52
57
  def set_enrollment_secret
53
- @secret = (session[:mfa_enrollment_secret] ||= Loam::Totp.generate_secret)
54
- @provisioning_uri = Loam::Totp.provisioning_uri(@secret, account: current_actor.email, issuer: ISSUER)
58
+ @secret = (session[:mfa_enrollment_secret] ||= OpenLoam::Totp.generate_secret)
59
+ @provisioning_uri = OpenLoam::Totp.provisioning_uri(@secret, account: current_actor.email, issuer: ISSUER)
55
60
  end
56
61
 
57
62
  def active_mfa?
58
- Loam::MfaCredential.active_for(current_actor).present?
63
+ OpenLoam::MfaCredential.active_for(current_actor).present?
59
64
  end
60
65
  end
61
66
  end
@@ -1,10 +1,12 @@
1
1
  module Admin
2
2
  # The bell. Every query here is scoped to the current actor, so there is no
3
3
  # "read someone else's notifications" path to authorize and no per-record
4
- # policy to write: tenancy is structural (Loam::Notification is a
5
- # Loam::TenantRecord) and BaseController has already established that this
4
+ # policy to write: tenancy is structural (OpenLoam::Notification is a
5
+ # OpenLoam::TenantRecord) and BaseController has already established that this
6
6
  # actor is signed in to this tenant. Recipient scoping is the whole rule.
7
7
  class NotificationsController < BaseController
8
+ skip_authorization! "Scoped to current_actor — there is no path to another user's notifications."
9
+
8
10
  def index
9
11
  @records = notifications.order(created_at: :desc).limit(100)
10
12
  end
@@ -17,7 +19,7 @@ module Admin
17
19
  private
18
20
 
19
21
  def notifications
20
- Loam::Notification.where(user_id: current_actor.id)
22
+ OpenLoam::Notification.where(user_id: current_actor.id)
21
23
  end
22
24
  end
23
25
  end
@@ -1,5 +1,5 @@
1
1
  module Admin
2
- # Read-only view of the app's Loam::Overrides — which registry entries are
2
+ # Read-only view of the app's OpenLoam::Overrides — which registry entries are
3
3
  # disabled or replaced, plus any STALE overrides (a key that no longer exists).
4
4
  # Manager-only, for legibility: it's config declared in an initializer, shown
5
5
  # here so an operator can see what's been customized without reading code.
@@ -7,8 +7,8 @@ module Admin
7
7
  before_action { require_role!(:manager) }
8
8
 
9
9
  def index
10
- @overrides = Loam::Overrides.all
11
- @stale = Loam::Overrides.stale
10
+ @overrides = OpenLoam::Overrides.all
11
+ @stale = OpenLoam::Overrides.stale
12
12
  end
13
13
  end
14
14
  end
@@ -1,7 +1,7 @@
1
1
  <h1>Overrides</h1>
2
2
 
3
3
  <p>Registry entries this app disables or replaces (declared in
4
- <code>config/initializers/loam.rb</code> via <code>Loam::Overrides</code>) —
4
+ <code>config/initializers/open_loam.rb</code> via <code>OpenLoam::Overrides</code>) —
5
5
  customization without forking. Structural pieces (views, controllers, routes)
6
6
  are overridden the Rails way (path shadowing), not here.</p>
7
7
 
@@ -18,7 +18,7 @@ are overridden the Rails way (path shadowing), not here.</p>
18
18
  <tr>
19
19
  <td><code><%%= registry %></code></td>
20
20
  <td><code><%%= key %></code></td>
21
- <td><%%= value == Loam::Overrides::DISABLED ? "disabled" : "replaced" %></td>
21
+ <td><%%= value == OpenLoam::Overrides::DISABLED ? "disabled" : "replaced" %></td>
22
22
  </tr>
23
23
  <%% end %>
24
24
  <%% end %>
@@ -1,5 +1,5 @@
1
1
  module Admin
2
- # The approval queue: staged mutations (Loam::PendingAction) waiting on a
2
+ # The approval queue: staged mutations (OpenLoam::PendingAction) waiting on a
3
3
  # human. Manager-only — approving EXECUTES the change. The workflow's role gate
4
4
  # is the deeper enforcement (approve!/reject! raise for a non-manager); this
5
5
  # screen is a manager screen on top of it.
@@ -8,7 +8,7 @@ module Admin
8
8
  before_action :set_pending_action, only: %i[approve reject]
9
9
 
10
10
  def index
11
- @pending = Loam::PendingAction.pending.order(created_at: :desc)
11
+ @pending = OpenLoam::PendingAction.pending.order(created_at: :desc)
12
12
  end
13
13
 
14
14
  def approve
@@ -24,7 +24,7 @@ module Admin
24
24
  private
25
25
 
26
26
  def set_pending_action
27
- @pending_action = Loam::PendingAction.find(params[:id])
27
+ @pending_action = OpenLoam::PendingAction.find(params[:id])
28
28
  end
29
29
 
30
30
  def approval_notice(pending)
@@ -1,13 +1,18 @@
1
1
  module Admin
2
- # Managing saved views (Loam::Perspective) for an entity. A view is CREATED by
2
+ # Managing saved views (OpenLoam::Perspective) for an entity. A view is CREATED by
3
3
  # "save current view" on the entity index (capturing the live filters/sort); it
4
4
  # starts private, owned by you. Here you rename it, widen its audience, make it
5
5
  # the default, or delete it.
6
6
  #
7
7
  # Who may manage a view: its owner always; a manager for role/tenant-visibility
8
8
  # ones. A private view of someone else's is invisible here by construction
9
- # (Loam::Perspectives.visible_to never returns it).
9
+ # (OpenLoam::Perspectives.visible_to never returns it).
10
10
  class PerspectivesController < BaseController
11
+ # update/destroy/set_default authorize via authorize_manage!. index and create
12
+ # act only on the actor's own private views, so there is no subject to gate.
13
+ skip_authorization! "index lists views visible to the actor; create only ever makes a private one, owned by them.",
14
+ only: %i[index create]
15
+
11
16
  before_action :set_perspective, only: %i[update destroy set_default]
12
17
  before_action :authorize_manage!, only: %i[update destroy set_default]
13
18
 
@@ -18,12 +23,12 @@ module Admin
18
23
 
19
24
  def index
20
25
  @entity_type = params[:entity_type].to_s
21
- @perspectives = Loam::Perspectives.visible_to(@entity_type, user: current_actor)
26
+ @perspectives = OpenLoam::Perspectives.visible_to(@entity_type, user: current_actor)
22
27
  end
23
28
 
24
29
  # From the entity index: save the CURRENT filters/sort as a new private view.
25
30
  def create
26
- perspective = Loam::Perspective.create!(
31
+ perspective = OpenLoam::Perspective.create!(
27
32
  entity_type: params[:entity_type].to_s,
28
33
  name: params[:name].presence || "Saved view",
29
34
  visibility: "private",
@@ -38,7 +43,7 @@ module Admin
38
43
  # the audience clears the default flag so it is re-chosen deliberately.
39
44
  attributes = perspective_params
40
45
  if attributes[:visibility].present? && attributes[:visibility] != @perspective.visibility
41
- raise Loam::NotAuthorizedError if attributes[:visibility] != "private" && current_role != :manager
46
+ raise OpenLoam::NotAuthorizedError if attributes[:visibility] != "private" && current_role != :manager
42
47
 
43
48
  attributes[:is_default] = false
44
49
  end
@@ -64,11 +69,12 @@ module Admin
64
69
  private
65
70
 
66
71
  def set_perspective
67
- @perspective = Loam::Perspective.find(params[:id])
72
+ @perspective = OpenLoam::Perspective.find(params[:id])
68
73
  end
69
74
 
70
75
  def authorize_manage!
71
- raise Loam::NotAuthorizedError unless can_manage?(@perspective)
76
+ authorized!
77
+ raise OpenLoam::NotAuthorizedError unless can_manage?(@perspective)
72
78
  end
73
79
 
74
80
  # Owner always; a manager for shared (role/tenant) views. A private view of
@@ -24,7 +24,7 @@ or delete it.</p>
24
24
  <%% if perspective.owner_id == current_actor.id || (perspective.visibility != "private" && current_role == :manager) %>
25
25
  <%%= form_with url: admin_perspective_path(perspective, entity_type: @entity_type), method: :patch, class: "inline" do %>
26
26
  <%%= text_field_tag :"perspective[name]", perspective.name, size: 16 %>
27
- <%%= select_tag :"perspective[visibility]", options_for_select(Loam::Perspective::VISIBILITIES, perspective.visibility) %>
27
+ <%%= select_tag :"perspective[visibility]", options_for_select(OpenLoam::Perspective::VISIBILITIES, perspective.visibility) %>
28
28
  <%%= text_field_tag :"perspective[role]", perspective.role, placeholder: "role", size: 8 %>
29
29
  <%%= submit_tag "Save" %>
30
30
  <%% end %>
@@ -0,0 +1,23 @@
1
+ module Admin
2
+ # Long-running task progress (OpenLoam::ProgressJob) — a tenant-scoped list of
3
+ # recent jobs with a live bar (the open_loam.progress.updated SSE event updates it).
4
+ # Your app starts jobs with OpenLoam::Progress.start in its own background jobs;
5
+ # a running one can be cancelled cooperatively here.
6
+ class ProgressJobsController < BaseController
7
+ skip_authorization! "A tenant-scoped list of the tenant's own jobs; cancel is the gated action.",
8
+ only: :index
9
+
10
+ def index
11
+ @jobs = OpenLoam::ProgressJob.recent.limit(20)
12
+ end
13
+
14
+ def cancel
15
+ job = OpenLoam::ProgressJob.find(params[:id])
16
+ # Manager-or-owner: a member must not cancel another user's job.
17
+ authorized!
18
+ raise OpenLoam::NotAuthorizedError unless current_role == :manager || job.actor_id == current_actor&.id
19
+ job.cancel! if job.running?
20
+ redirect_to admin_progress_jobs_path, notice: "Job cancelled."
21
+ end
22
+ end
23
+ end
@@ -1,7 +1,7 @@
1
1
  <h1>Tasks</h1>
2
2
 
3
3
  <p>Long-running jobs report progress here live (no reload — pushed over SSE).
4
- Start one from your own code with <code>Loam::Progress.start(name:, total:)</code>.</p>
4
+ Start one from your own code with <code>OpenLoam::Progress.start(name:, total:)</code>.</p>
5
5
 
6
6
  <table>
7
7
  <thead>
@@ -1,16 +1,16 @@
1
1
  module Admin
2
- # Manager take-over of an advisory edit lock (Loam::RecordLocks). Only a
2
+ # Manager take-over of an advisory edit lock (OpenLoam::RecordLocks). Only a
3
3
  # manager may force-release someone else's lock; the record it points at is
4
- # resolved through a whitelist (a Loam::TenantRecord subclass), never a raw
4
+ # resolved through a whitelist (a OpenLoam::TenantRecord subclass), never a raw
5
5
  # constantize of user input.
6
6
  class RecordLocksController < BaseController
7
7
  before_action { require_role!(:manager) }
8
8
 
9
9
  def destroy
10
10
  klass = params[:lockable_type].to_s.safe_constantize
11
- raise Loam::NotAuthorizedError unless klass && klass < Loam::TenantRecord
11
+ raise OpenLoam::NotAuthorizedError unless klass && klass < OpenLoam::TenantRecord
12
12
 
13
- Loam::RecordLocks.force_release(klass.find(params[:lockable_id]))
13
+ OpenLoam::RecordLocks.force_release(klass.find(params[:lockable_id]))
14
14
  redirect_back fallback_location: admin_root_path, notice: "Lock released — you can edit now."
15
15
  end
16
16
  end