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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +148 -8
- data/LICENSE +1 -1
- data/README.md +27 -25
- data/app/jobs/{loam → open_loam}/custom_field_reindex_job.rb +6 -6
- data/app/jobs/{loam → open_loam}/event_delivery_job.rb +8 -8
- data/app/jobs/open_loam/event_log_prune_job.rb +15 -0
- data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
- data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +17 -8
- data/app/models/open_loam/api_token.rb +63 -0
- data/app/models/{loam → open_loam}/audit_record.rb +3 -3
- data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
- data/app/models/{loam → open_loam}/business_rule.rb +10 -10
- data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
- data/app/models/{loam → open_loam}/comment.rb +9 -9
- data/app/models/{loam → open_loam}/config.rb +7 -7
- data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
- data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
- data/app/models/open_loam/dictionary.rb +20 -0
- data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
- data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
- data/app/models/open_loam/event_record.rb +42 -0
- data/app/models/{loam → open_loam}/field_definition.rb +8 -8
- data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
- data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
- data/app/models/{loam → open_loam}/membership.rb +6 -6
- data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
- data/app/models/{loam → open_loam}/notification.rb +10 -10
- data/app/models/{loam → open_loam}/pending_action.rb +70 -26
- data/app/models/{loam → open_loam}/perspective.rb +5 -5
- data/app/models/{loam → open_loam}/progress_job.rb +7 -7
- data/app/models/{loam → open_loam}/record_lock.rb +5 -5
- data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
- data/app/models/open_loam/search_token.rb +9 -0
- data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
- data/app/models/open_loam/sso_provider.rb +66 -0
- data/app/models/open_loam/tenant.rb +20 -0
- data/app/models/{loam → open_loam}/translation.rb +5 -5
- data/app/models/open_loam/webhook_endpoint.rb +39 -0
- data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
- data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
- data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +6 -2
- data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
- data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +2 -1
- data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +32 -22
- data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
- data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
- data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
- data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
- data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +8 -5
- data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
- data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/index.html.erb +94 -0
- data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/show.html.erb +38 -0
- data/lib/generators/{loam → open_loam}/install/install_generator.rb +58 -47
- data/lib/generators/open_loam/install/templates/AGENTS.md +345 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
- data/lib/generators/open_loam/install/templates/admin/api_tokens_controller.rb +48 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +23 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +101 -38
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
- data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +12 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
- data/lib/generators/open_loam/install/templates/admin/history_controller.rb +64 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +25 -13
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -4
- data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +11 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +5 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +13 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
- data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +23 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +33 -34
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +8 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
- data/lib/generators/open_loam/install/templates/api_base_controller.rb +127 -0
- data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
- data/lib/generators/open_loam/install/templates/import_job.rb +38 -0
- data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +87 -75
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
- 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
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
- 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
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_event_records.rb +13 -0
- 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
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
- 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
- data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
- data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
- data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
- data/lib/open-loam.rb +2 -2
- data/lib/{loam → open_loam}/attachable.rb +4 -4
- data/lib/{loam → open_loam}/auditable.rb +23 -23
- data/lib/{loam → open_loam}/auth_throttle.rb +20 -12
- data/lib/{loam → open_loam}/base32.rb +1 -1
- data/lib/{loam → open_loam}/bulk.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
- data/lib/{loam → open_loam}/business_rules.rb +19 -19
- data/lib/{loam → open_loam}/commentable.rb +6 -6
- data/lib/{loam → open_loam}/configs.rb +19 -19
- data/lib/{loam → open_loam}/cron.rb +2 -2
- data/lib/{loam → open_loam}/csv.rb +2 -2
- data/lib/{loam → open_loam}/current.rb +5 -5
- data/lib/{loam → open_loam}/custom_field_index.rb +33 -30
- data/lib/{loam → open_loam}/custom_fields.rb +17 -17
- data/lib/{loam → open_loam}/dashboard.rb +5 -5
- data/lib/{loam → open_loam}/dictionaries.rb +12 -12
- data/lib/{loam → open_loam}/durable_events.rb +19 -19
- data/lib/{loam → open_loam}/encryptable.rb +39 -34
- data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
- data/lib/{loam → open_loam}/encryption/key_provider.rb +19 -3
- data/lib/{loam → open_loam}/encryption.rb +58 -15
- data/lib/open_loam/engine.rb +65 -0
- data/lib/{loam → open_loam}/enrichers.rb +5 -5
- data/lib/{loam → open_loam}/errors.rb +20 -10
- data/lib/{loam → open_loam}/eval.rb +2 -2
- data/lib/open_loam/event_log.rb +77 -0
- data/lib/{loam → open_loam}/event_stream.rb +14 -14
- data/lib/{loam → open_loam}/eventful.rb +12 -12
- data/lib/{loam → open_loam}/events.rb +5 -5
- data/lib/{loam → open_loam}/export.rb +12 -12
- data/lib/{loam → open_loam}/features.rb +15 -15
- data/lib/{loam → open_loam}/generated_key.rb +6 -6
- data/lib/{loam → open_loam}/import.rb +12 -15
- data/lib/{loam → open_loam}/inbound_webhooks.rb +19 -18
- data/lib/{loam → open_loam}/lifecycle.rb +54 -25
- data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
- data/lib/{loam → open_loam}/mcp/server.rb +9 -9
- data/lib/{loam → open_loam}/mcp.rb +31 -22
- data/lib/{loam → open_loam}/notifications.rb +5 -5
- data/lib/{loam → open_loam}/open_api.rb +16 -16
- data/lib/open_loam/outbound_url.rb +100 -0
- data/lib/{loam → open_loam}/overrides.rb +10 -10
- data/lib/{loam → open_loam}/pending_actions.rb +9 -9
- data/lib/{loam → open_loam}/permissions.rb +8 -8
- data/lib/{loam → open_loam}/perspectives.rb +9 -9
- data/lib/{loam → open_loam}/policy.rb +21 -8
- data/lib/{loam → open_loam}/progress.rb +6 -6
- data/lib/{loam → open_loam}/record_locks.rb +9 -9
- data/lib/{loam → open_loam}/scheduler.rb +20 -20
- data/lib/{loam → open_loam}/search/driver.rb +3 -3
- data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
- data/lib/{loam → open_loam}/search/token_driver.rb +16 -16
- data/lib/{loam → open_loam}/search.rb +5 -5
- data/lib/{loam → open_loam}/searchable.rb +22 -22
- data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
- data/lib/{loam → open_loam}/sso/claims.rb +1 -1
- data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
- data/lib/open_loam/sso/http_client.rb +51 -0
- data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
- data/lib/{loam → open_loam}/sso.rb +39 -18
- data/lib/{loam → open_loam}/telemetry.rb +6 -6
- data/lib/{loam → open_loam}/tenant_record.rb +10 -10
- data/lib/{loam → open_loam}/test_helpers.rb +4 -4
- data/lib/{loam → open_loam}/totp.rb +3 -3
- data/lib/{loam → open_loam}/translatable.rb +24 -24
- data/lib/{loam → open_loam}/undo.rb +11 -11
- data/lib/open_loam/version.rb +3 -0
- data/lib/{loam → open_loam}/webhooks.rb +4 -4
- data/lib/{loam → open_loam}/widgets.rb +9 -9
- data/lib/{loam → open_loam}/workflow.rb +45 -45
- data/lib/open_loam.rb +124 -0
- data/lib/tasks/open_loam.rake +203 -0
- data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
- data/lib/tasks/open_loam_mcp.rake +20 -0
- metadata +247 -242
- data/app/models/loam/api_token.rb +0 -40
- data/app/models/loam/dictionary.rb +0 -20
- data/app/models/loam/search_token.rb +0 -9
- data/app/models/loam/sso_provider.rb +0 -35
- data/app/models/loam/tenant.rb +0 -20
- data/app/models/loam/webhook_endpoint.rb +0 -26
- data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
- data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
- data/lib/generators/loam/install/templates/AGENTS.md +0 -341
- data/lib/generators/loam/install/templates/admin/api_tokens_controller.rb +0 -30
- data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
- data/lib/generators/loam/install/templates/admin/history_controller.rb +0 -38
- data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
- data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
- data/lib/generators/loam/install/templates/api_base_controller.rb +0 -90
- data/lib/generators/loam/install/templates/import_job.rb +0 -25
- data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
- data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
- data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
- data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
- data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
- data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
- data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
- data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
- data/lib/loam/engine.rb +0 -57
- data/lib/loam/sso/http_client.rb +0 -42
- data/lib/loam/version.rb +0 -3
- data/lib/loam.rb +0 -122
- data/lib/tasks/loam.rake +0 -171
- data/lib/tasks/loam_mcp.rake +0 -20
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Undo/redo built ON the audit trail (L-704). Every change already records a
|
|
3
|
-
#
|
|
3
|
+
# OpenLoam::AuditRecord with its changeset; undoing one applies the inverse and
|
|
4
4
|
# writes a NEW audit record — so the inverse is itself undoable, and "redo" is
|
|
5
5
|
# just undoing that undo entry. No separate command log to keep in sync.
|
|
6
6
|
#
|
|
@@ -12,11 +12,11 @@ module Loam
|
|
|
12
12
|
# * encrypted fields — the audit stores "[encrypted]", never the old value,
|
|
13
13
|
# so there is nothing to revert to;
|
|
14
14
|
# * the workflow column — a direct write is blocked by the transition gate
|
|
15
|
-
# (
|
|
15
|
+
# (OpenLoam::Workflow), so state is undone by performing the reverse TRANSITION,
|
|
16
16
|
# not here;
|
|
17
17
|
# * fields the actor's role may not write (when a policy is supplied).
|
|
18
18
|
module Undo
|
|
19
|
-
class NotUndoableError <
|
|
19
|
+
class NotUndoableError < OpenLoam::Error; end
|
|
20
20
|
|
|
21
21
|
IGNORE = %w[id lock_version created_at updated_at].freeze
|
|
22
22
|
|
|
@@ -53,7 +53,7 @@ module Loam
|
|
|
53
53
|
|
|
54
54
|
def revert_fields(record, audit, policy)
|
|
55
55
|
ensure_latest!(audit)
|
|
56
|
-
workflow_column = record.class.respond_to?(:
|
|
56
|
+
workflow_column = record.class.respond_to?(:open_loam_workflow) ? record.class.open_loam_workflow&.column.to_s : nil
|
|
57
57
|
|
|
58
58
|
updates = {}
|
|
59
59
|
(audit.changeset || {}).each do |field, values|
|
|
@@ -69,10 +69,10 @@ module Loam
|
|
|
69
69
|
raise NotUndoableError, "nothing revertable in this change" if updates.empty?
|
|
70
70
|
|
|
71
71
|
# Relabel this save's audit as "undo" so the history reads legibly (and the
|
|
72
|
-
# entry is itself undoable = redo).
|
|
72
|
+
# entry is itself undoable = redo). open_loam_audit_as is the same hook
|
|
73
73
|
# SoftDeletable uses for soft_delete/restore.
|
|
74
|
-
if record.respond_to?(:
|
|
75
|
-
record.send(:
|
|
74
|
+
if record.respond_to?(:open_loam_audit_as, true)
|
|
75
|
+
record.send(:open_loam_audit_as, "undo") { record.update!(updates) }
|
|
76
76
|
else
|
|
77
77
|
record.update!(updates)
|
|
78
78
|
end
|
|
@@ -92,7 +92,7 @@ module Loam
|
|
|
92
92
|
# The audit must be the record's most recent update/undo — else undoing it
|
|
93
93
|
# would overwrite newer edits. Tenant-scoped via AuditRecord's default scope.
|
|
94
94
|
def ensure_latest!(audit)
|
|
95
|
-
latest =
|
|
95
|
+
latest = OpenLoam::AuditRecord
|
|
96
96
|
.where(auditable_type: audit.auditable_type, auditable_id: audit.auditable_id, action: %w[update undo])
|
|
97
97
|
.order(:id).last
|
|
98
98
|
return if latest.nil? || latest.id == audit.id
|
|
@@ -105,7 +105,7 @@ module Loam
|
|
|
105
105
|
# restore. Never AuditRecord#auditable, which is unscoped.
|
|
106
106
|
def load_record(audit)
|
|
107
107
|
klass = audit.auditable_type.safe_constantize
|
|
108
|
-
raise NotUndoableError, "unknown type #{audit.auditable_type.inspect}" unless klass.is_a?(Class) && klass <
|
|
108
|
+
raise NotUndoableError, "unknown type #{audit.auditable_type.inspect}" unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord
|
|
109
109
|
|
|
110
110
|
scope = klass.respond_to?(:with_deleted) ? klass.with_deleted : klass.all
|
|
111
111
|
record = scope.find_by(id: audit.auditable_id)
|
|
@@ -120,7 +120,7 @@ module Loam
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
def ensure_current_tenant!(audit)
|
|
123
|
-
raise NotUndoableError, "not in this tenant" unless audit.tenant_id ==
|
|
123
|
+
raise NotUndoableError, "not in this tenant" unless audit.tenant_id == OpenLoam.tenant!.id
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Domain events, delivered over HTTP to whoever asked for them.
|
|
3
3
|
#
|
|
4
|
-
# One global subscriber listens to every
|
|
4
|
+
# One global subscriber listens to every OpenLoam event and, in the tenant the
|
|
5
5
|
# event was published in, enqueues a delivery per matching active endpoint.
|
|
6
6
|
# Matching happens at dispatch time rather than at subscribe time because
|
|
7
7
|
# endpoints are data: a tenant can add one from the admin without a reboot.
|
|
8
8
|
module Webhooks
|
|
9
|
-
# Wired once from
|
|
9
|
+
# Wired once from OpenLoam::Engine. Idempotent: subscribing twice would deliver
|
|
10
10
|
# every event twice.
|
|
11
11
|
def self.subscribe!
|
|
12
12
|
@subscription ||= Events.subscribe_all { |event_name, payload| dispatch(event_name, payload) }
|
|
@@ -22,7 +22,7 @@ module Loam
|
|
|
22
22
|
# and scalars by convention, never records.
|
|
23
23
|
deliverable = payload.transform_keys(&:to_s)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
OpenLoam.as_tenant(tenant) do
|
|
26
26
|
WebhookEndpoint.active.each do |endpoint|
|
|
27
27
|
next unless endpoint.matches?(event_name)
|
|
28
28
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A registry of dashboard widgets — small, module-provided tiles (a metric, a
|
|
3
3
|
# short list) shown on the admin home. A widget is a DATA PROVIDER plus a
|
|
4
4
|
# title and an optional role filter; it never renders arbitrary code:
|
|
5
5
|
#
|
|
6
|
-
#
|
|
6
|
+
# OpenLoam::Widgets.register(key: "open_rentals", title: "Open rentals", roles: %w[manager]) do |actor|
|
|
7
7
|
# { kind: "count", value: Rental.where(status: "open").count } # tenant-scoped query
|
|
8
8
|
# end
|
|
9
9
|
#
|
|
@@ -37,33 +37,33 @@ module Loam
|
|
|
37
37
|
# can't see it (so its data is never computed), otherwise
|
|
38
38
|
# { key, title, data } — or { key, title, error: } when the provider raises.
|
|
39
39
|
def resolve(key, actor:, role:)
|
|
40
|
-
return nil if
|
|
40
|
+
return nil if OpenLoam::Overrides.disabled?(:widgets, key) # customization without forking
|
|
41
41
|
|
|
42
42
|
widget = find(key)
|
|
43
43
|
return nil unless widget&.visible_to?(role)
|
|
44
44
|
|
|
45
|
-
provider =
|
|
45
|
+
provider = OpenLoam::Overrides.replacement(:widgets, key) || widget.provider
|
|
46
46
|
{ key: widget.key, title: widget.title, data: provider.call(actor) }
|
|
47
47
|
rescue StandardError => error
|
|
48
48
|
{ key: widget&.key, title: widget&.title, error: error.message }
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
# The widgets every
|
|
51
|
+
# The widgets every OpenLoam app starts with — registered at boot from the
|
|
52
52
|
# engine, so a fresh install has a useful default dashboard.
|
|
53
53
|
def register_builtins!
|
|
54
54
|
register(key: "audit_recent", title: "Recent activity") do |_actor|
|
|
55
|
-
items =
|
|
55
|
+
items = OpenLoam::AuditRecord.order(created_at: :desc).limit(8)
|
|
56
56
|
.map { |a| "#{a.action} #{a.auditable_type} ##{a.auditable_id}" }
|
|
57
57
|
{ kind: "list", items: items }
|
|
58
58
|
end
|
|
59
59
|
register(key: "notifications_unread", title: "Unread notifications") do |actor|
|
|
60
|
-
{ kind: "count", value:
|
|
60
|
+
{ kind: "count", value: OpenLoam::Notification.unread.where(user_id: actor&.id).count }
|
|
61
61
|
end
|
|
62
62
|
register(key: "pending_approvals", title: "Pending approvals", roles: %w[manager]) do |_actor|
|
|
63
|
-
{ kind: "count", value:
|
|
63
|
+
{ kind: "count", value: OpenLoam::PendingAction.pending.count }
|
|
64
64
|
end
|
|
65
65
|
register(key: "open_progress", title: "Running tasks") do |_actor|
|
|
66
|
-
{ kind: "count", value:
|
|
66
|
+
{ kind: "count", value: OpenLoam::ProgressJob.where(status: "running").count }
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Declarative state machine for a tenant-scoped entity: the states a record
|
|
3
3
|
# can be in, the legal moves between them, and who may make each move.
|
|
4
4
|
#
|
|
5
|
-
# class PurchaseOrder <
|
|
6
|
-
# include
|
|
5
|
+
# class PurchaseOrder < OpenLoam::TenantRecord
|
|
6
|
+
# include OpenLoam::Workflow
|
|
7
7
|
#
|
|
8
8
|
# workflow :status, initial: "draft" do
|
|
9
9
|
# state "draft"
|
|
@@ -18,14 +18,14 @@ module Loam
|
|
|
18
18
|
# order.submit! # draft -> pending_approval, publishes
|
|
19
19
|
# # "<domain>.purchase_order.submit"
|
|
20
20
|
# order.workflow_transitions_available # => [:approve] for a manager, [] for anyone else
|
|
21
|
-
# PurchaseOrder.
|
|
21
|
+
# PurchaseOrder.open_loam_workflow # the frozen definition, for agents and admin UI
|
|
22
22
|
#
|
|
23
23
|
# What this buys over hand-rolled `if status == "draft"`: an illegal move
|
|
24
24
|
# raises instead of silently writing a bad value, the roles that may move a
|
|
25
25
|
# record are declared in one readable place instead of scattered across
|
|
26
26
|
# controllers, and the whole machine is introspectable.
|
|
27
27
|
#
|
|
28
|
-
# The state change itself is an ordinary attribute write, so
|
|
28
|
+
# The state change itself is an ordinary attribute write, so OpenLoam::Auditable
|
|
29
29
|
# records it like any other change — a workflow adds no second audit trail.
|
|
30
30
|
module Workflow
|
|
31
31
|
extend ActiveSupport::Concern
|
|
@@ -99,22 +99,22 @@ module Loam
|
|
|
99
99
|
|
|
100
100
|
included do
|
|
101
101
|
# nil until `workflow` is called; inherited by subclasses.
|
|
102
|
-
class_attribute :
|
|
102
|
+
class_attribute :open_loam_workflow, instance_writer: false, default: nil
|
|
103
103
|
|
|
104
104
|
# THE gate: the workflow column may only change through a transition
|
|
105
|
-
# (
|
|
105
|
+
# (open_loam_perform_transition!), which enforces from-state and role. A direct
|
|
106
106
|
# write — the edit form, Bulk.set_field, an import, a business-rule
|
|
107
107
|
# set_field — would otherwise let a member self-"approve", skipping the
|
|
108
108
|
# transition's roles:. Closing it here closes ALL paths at once. Creation
|
|
109
109
|
# (the initial state) is exempt (`on: :update`).
|
|
110
|
-
validate :
|
|
110
|
+
validate :open_loam_workflow_column_only_via_transition, on: :update
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
class_methods do
|
|
114
114
|
# The DSL. `initial:` defaults to the first declared state.
|
|
115
115
|
def workflow(column, initial: nil, &block)
|
|
116
116
|
definition = Builder.new(column, initial).build(&block)
|
|
117
|
-
self.
|
|
117
|
+
self.open_loam_workflow = definition
|
|
118
118
|
|
|
119
119
|
before_validation on: :create do
|
|
120
120
|
self[definition.column] = definition.initial if self[definition.column].blank?
|
|
@@ -125,29 +125,29 @@ module Loam
|
|
|
125
125
|
validates definition.column,
|
|
126
126
|
inclusion: { in: definition.states, message: "is not one of: #{definition.states.join(', ')}" }
|
|
127
127
|
|
|
128
|
-
include
|
|
128
|
+
include open_loam_workflow_module(definition)
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
# Transition and predicate methods live in their own module rather than
|
|
132
132
|
# on the class, so an app can override `submit!` and still call `super`.
|
|
133
|
-
def
|
|
133
|
+
def open_loam_workflow_module(definition)
|
|
134
134
|
model = self
|
|
135
135
|
|
|
136
136
|
Module.new do
|
|
137
137
|
definition.transitions.each_value do |transition|
|
|
138
|
-
define_method("#{transition.name}!") {
|
|
138
|
+
define_method("#{transition.name}!") { open_loam_perform_transition!(transition) }
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
definition.states.each do |state|
|
|
142
142
|
predicate = "#{state}?"
|
|
143
|
-
next unless model.
|
|
143
|
+
next unless model.open_loam_workflow_predicate_free?(predicate)
|
|
144
144
|
|
|
145
145
|
# A state predicate must never shadow a real column: DamageReport
|
|
146
146
|
# has both a workflow state "approved" and an `approved` boolean
|
|
147
147
|
# column, and `record.approved?` has to stay the column's. The
|
|
148
148
|
# column check happens at CALL time, not class-load time — at load
|
|
149
149
|
# the schema may not be readable yet (fresh CI database, db:create),
|
|
150
|
-
# and a load-time decision made
|
|
150
|
+
# and a load-time decision made OpenLoam behave differently on CI than
|
|
151
151
|
# on a warmed-up dev machine.
|
|
152
152
|
define_method(predicate) do
|
|
153
153
|
if self.class.columns_hash.key?(state)
|
|
@@ -162,41 +162,41 @@ module Loam
|
|
|
162
162
|
|
|
163
163
|
# Explicitly defined methods still win — only truly free names get a
|
|
164
164
|
# workflow predicate. Column collisions are handled inside the predicate
|
|
165
|
-
# itself (see
|
|
166
|
-
def
|
|
165
|
+
# itself (see open_loam_workflow_module), where the schema is reliably known.
|
|
166
|
+
def open_loam_workflow_predicate_free?(predicate)
|
|
167
167
|
!method_defined?(predicate) && !private_method_defined?(predicate)
|
|
168
168
|
end
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
# Transition names that are legal right now: from this record's state, for
|
|
172
|
-
# the actor in
|
|
172
|
+
# the actor in OpenLoam::Current. Never raises — an admin screen asks this to
|
|
173
173
|
# decide which buttons to render.
|
|
174
174
|
def workflow_transitions_available
|
|
175
|
-
role =
|
|
175
|
+
role = open_loam_workflow_role
|
|
176
176
|
|
|
177
|
-
self.class.
|
|
177
|
+
self.class.open_loam_workflow.transitions_from(open_loam_workflow_state)
|
|
178
178
|
.select { |transition| transition.roles.empty? || transition.roles.include?(role) }
|
|
179
179
|
.map(&:name)
|
|
180
180
|
end
|
|
181
181
|
|
|
182
|
-
def
|
|
183
|
-
self[self.class.
|
|
182
|
+
def open_loam_workflow_state
|
|
183
|
+
self[self.class.open_loam_workflow.column].to_s
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
private
|
|
187
187
|
|
|
188
|
-
def
|
|
189
|
-
return unless self.class.
|
|
188
|
+
def open_loam_workflow_column_only_via_transition
|
|
189
|
+
return unless self.class.open_loam_workflow
|
|
190
190
|
|
|
191
|
-
column = self.class.
|
|
191
|
+
column = self.class.open_loam_workflow.column
|
|
192
192
|
return unless attribute_changed?(column)
|
|
193
|
-
return if @
|
|
193
|
+
return if @open_loam_in_transition
|
|
194
194
|
|
|
195
195
|
errors.add(column, "can only change through a workflow transition, not a direct write")
|
|
196
196
|
end
|
|
197
197
|
|
|
198
|
-
def
|
|
199
|
-
from =
|
|
198
|
+
def open_loam_perform_transition!(transition)
|
|
199
|
+
from = open_loam_workflow_state
|
|
200
200
|
|
|
201
201
|
unless transition.from.include?(from)
|
|
202
202
|
raise InvalidTransitionError,
|
|
@@ -204,53 +204,53 @@ module Loam
|
|
|
204
204
|
"but this record is #{from.inspect}"
|
|
205
205
|
end
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
open_loam_authorize_transition!(transition)
|
|
208
208
|
|
|
209
|
-
self[self.class.
|
|
209
|
+
self[self.class.open_loam_workflow.column] = transition.to
|
|
210
210
|
begin
|
|
211
|
-
@
|
|
211
|
+
@open_loam_in_transition = true # tells the guard THIS column change is blessed
|
|
212
212
|
save!
|
|
213
213
|
ensure
|
|
214
|
-
@
|
|
214
|
+
@open_loam_in_transition = false
|
|
215
215
|
end
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
"#{
|
|
217
|
+
OpenLoam::Events.publish(
|
|
218
|
+
"#{open_loam_workflow_event_domain}.#{model_name.param_key}.#{transition.name}",
|
|
219
219
|
id: id, from: from, to: transition.to
|
|
220
220
|
)
|
|
221
221
|
|
|
222
222
|
self
|
|
223
223
|
end
|
|
224
224
|
|
|
225
|
-
def
|
|
225
|
+
def open_loam_authorize_transition!(transition)
|
|
226
226
|
return if transition.roles.empty?
|
|
227
227
|
|
|
228
228
|
allowed = transition.roles.join(", ")
|
|
229
|
-
unless
|
|
229
|
+
unless OpenLoam::Current.actor
|
|
230
230
|
raise NotAuthorizedError,
|
|
231
231
|
"#{self.class.name}##{transition.name}! is restricted to #{allowed}, but there is no actor in " \
|
|
232
|
-
"
|
|
232
|
+
"OpenLoam::Current — wrap the call in OpenLoam.as_tenant(tenant, actor: user) { ... }"
|
|
233
233
|
end
|
|
234
234
|
|
|
235
|
-
return if transition.roles.include?(
|
|
235
|
+
return if transition.roles.include?(open_loam_workflow_role)
|
|
236
236
|
|
|
237
237
|
raise NotAuthorizedError,
|
|
238
238
|
"#{self.class.name}##{transition.name}! is restricted to #{allowed}; " \
|
|
239
|
-
"this actor's role is #{
|
|
239
|
+
"this actor's role is #{open_loam_workflow_role.inspect}"
|
|
240
240
|
end
|
|
241
241
|
|
|
242
|
-
# Role resolution deliberately goes through
|
|
242
|
+
# Role resolution deliberately goes through OpenLoam::Policy: "your role" means
|
|
243
243
|
# exactly one thing — your membership role in the current tenant — here, in
|
|
244
244
|
# policies, and in the admin.
|
|
245
|
-
def
|
|
246
|
-
actor =
|
|
247
|
-
actor &&
|
|
245
|
+
def open_loam_workflow_role
|
|
246
|
+
actor = OpenLoam::Current.actor
|
|
247
|
+
actor && OpenLoam::Policy.new(actor, self).role
|
|
248
248
|
end
|
|
249
249
|
|
|
250
250
|
# Transitions publish into the model's own event domain when it has one
|
|
251
|
-
# (
|
|
252
|
-
def
|
|
253
|
-
self.class.respond_to?(:
|
|
251
|
+
# (OpenLoam::Eventful), so workflow events sit beside the lifecycle events.
|
|
252
|
+
def open_loam_workflow_event_domain
|
|
253
|
+
self.class.respond_to?(:open_loam_event_domain) ? self.class.open_loam_event_domain : "app"
|
|
254
254
|
end
|
|
255
255
|
end
|
|
256
256
|
end
|
data/lib/open_loam.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require "open_loam/version"
|
|
2
|
+
require "open_loam/errors"
|
|
3
|
+
require "open_loam/current"
|
|
4
|
+
require "open_loam/events"
|
|
5
|
+
require "open_loam/policy"
|
|
6
|
+
require "open_loam/lifecycle"
|
|
7
|
+
require "open_loam/configs"
|
|
8
|
+
require "open_loam/features"
|
|
9
|
+
require "open_loam/auth_throttle"
|
|
10
|
+
require "open_loam/outbound_url"
|
|
11
|
+
require "open_loam/dictionaries"
|
|
12
|
+
require "open_loam/custom_field_index"
|
|
13
|
+
require "open_loam/undo"
|
|
14
|
+
require "open_loam/permissions"
|
|
15
|
+
require "open_loam/telemetry"
|
|
16
|
+
require "open_loam/eval"
|
|
17
|
+
require "open_loam/mcp"
|
|
18
|
+
require "open_loam/mcp/server"
|
|
19
|
+
require "open_loam/progress"
|
|
20
|
+
require "open_loam/cron"
|
|
21
|
+
require "open_loam/scheduler"
|
|
22
|
+
require "open_loam/csv"
|
|
23
|
+
require "open_loam/export"
|
|
24
|
+
require "open_loam/import"
|
|
25
|
+
require "open_loam/bulk"
|
|
26
|
+
require "open_loam/widgets"
|
|
27
|
+
require "open_loam/dashboard"
|
|
28
|
+
require "open_loam/overrides"
|
|
29
|
+
require "open_loam/open_api"
|
|
30
|
+
require "open_loam/encryption"
|
|
31
|
+
require "open_loam/base32"
|
|
32
|
+
require "open_loam/totp"
|
|
33
|
+
require "open_loam/pending_actions"
|
|
34
|
+
require "open_loam/perspectives"
|
|
35
|
+
require "open_loam/record_locks"
|
|
36
|
+
require "open_loam/event_stream"
|
|
37
|
+
require "open_loam/enrichers"
|
|
38
|
+
require "open_loam/search"
|
|
39
|
+
require "open_loam/search/driver"
|
|
40
|
+
require "open_loam/search/like_driver"
|
|
41
|
+
require "open_loam/search/token_driver"
|
|
42
|
+
require "open_loam/notifications"
|
|
43
|
+
require "open_loam/sso"
|
|
44
|
+
require "open_loam/business_rules"
|
|
45
|
+
require "open_loam/webhooks"
|
|
46
|
+
require "open_loam/inbound_webhooks"
|
|
47
|
+
require "open_loam/durable_events"
|
|
48
|
+
require "open_loam/event_log"
|
|
49
|
+
require "open_loam/engine" if defined?(Rails::Engine)
|
|
50
|
+
|
|
51
|
+
module OpenLoam
|
|
52
|
+
# The current tenant, or a loud failure. This is THE guardrail: tenant-scoped
|
|
53
|
+
# code paths call this, so a missing tenant context can never silently widen
|
|
54
|
+
# a query to all tenants.
|
|
55
|
+
def self.tenant!
|
|
56
|
+
Current.tenant or raise MissingTenantError
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.tenant
|
|
60
|
+
Current.tenant
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.actor
|
|
64
|
+
Current.actor
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Feature-string permission check for the current actor's role (see
|
|
68
|
+
# OpenLoam::Permissions) — deny-by-default, wildcard-aware. `role:` overrides the
|
|
69
|
+
# actor's role. Returns false with no actor/role.
|
|
70
|
+
def self.can?(permission, role: nil)
|
|
71
|
+
role ||= Current.actor && OpenLoam::Membership.find_by(user_id: Current.actor.id)&.role
|
|
72
|
+
OpenLoam::Permissions.allow?(role, permission)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The approval-gate seam. When a caller runs under :confirm (an MCP tool acting
|
|
76
|
+
# for an AI agent), a write should be STAGED for human approval via
|
|
77
|
+
# OpenLoam::PendingActions.stage instead of committed. OpenLoam does not intercept
|
|
78
|
+
# Active Record globally — this is the documented hook a write path checks.
|
|
79
|
+
def self.mutation_mode
|
|
80
|
+
Current.mutation_mode || :direct
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.require_confirmation?
|
|
84
|
+
mutation_mode == :confirm
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Run a block with writes gated for approval. The one blessed way to enter
|
|
88
|
+
# confirm-mode; restores the previous mode afterwards.
|
|
89
|
+
def self.with_confirmation
|
|
90
|
+
previous = Current.mutation_mode
|
|
91
|
+
Current.mutation_mode = :confirm
|
|
92
|
+
yield
|
|
93
|
+
ensure
|
|
94
|
+
Current.mutation_mode = previous
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Run a block inside a tenant (and optional actor) context, restoring the
|
|
98
|
+
# previous context afterwards. The one blessed way to switch tenants.
|
|
99
|
+
def self.as_tenant(tenant, actor: nil)
|
|
100
|
+
previous_tenant = Current.tenant
|
|
101
|
+
previous_actor = Current.actor
|
|
102
|
+
Current.tenant = tenant
|
|
103
|
+
Current.actor = actor if actor
|
|
104
|
+
yield
|
|
105
|
+
ensure
|
|
106
|
+
Current.tenant = previous_tenant
|
|
107
|
+
Current.actor = previous_actor
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
ActiveSupport.on_load(:active_record) do
|
|
112
|
+
require "open_loam/generated_key"
|
|
113
|
+
require "open_loam/tenant_record"
|
|
114
|
+
require "open_loam/auditable"
|
|
115
|
+
require "open_loam/soft_deletable"
|
|
116
|
+
require "open_loam/eventful"
|
|
117
|
+
require "open_loam/custom_fields"
|
|
118
|
+
require "open_loam/workflow"
|
|
119
|
+
require "open_loam/searchable"
|
|
120
|
+
require "open_loam/encryptable"
|
|
121
|
+
require "open_loam/translatable"
|
|
122
|
+
require "open_loam/commentable"
|
|
123
|
+
require "open_loam/attachable"
|
|
124
|
+
end
|