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,8 +1,8 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
module Generators
|
|
3
|
-
# Shared primary-key shaping for `
|
|
3
|
+
# Shared primary-key shaping for `open_loam:install` and `open_loam:entity`.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
5
|
+
# OpenLoam's migrations used to spell every key `bigint` — implicitly in
|
|
6
6
|
# `create_table`, and explicitly in `t.references` and the polymorphic
|
|
7
7
|
# `*_id` columns. In an app whose own tables use string or uuid keys the
|
|
8
8
|
# foreign keys then do not line up with the tables they point at, and every
|
|
@@ -10,7 +10,7 @@ module Loam
|
|
|
10
10
|
#
|
|
11
11
|
# The key type is taken from, in order: an explicit --primary-key-type, the
|
|
12
12
|
# host app's own `config.generators` setting (so an app that already told
|
|
13
|
-
# Rails what its keys look like does not have to tell
|
|
13
|
+
# Rails what its keys look like does not have to tell OpenLoam separately), and
|
|
14
14
|
# bigint as the Rails default.
|
|
15
15
|
#
|
|
16
16
|
# The templates ask for the *rendered fragment* rather than the type, so
|
|
@@ -34,15 +34,15 @@ module Loam
|
|
|
34
34
|
|
|
35
35
|
private
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
@
|
|
37
|
+
def open_loam_key_type
|
|
38
|
+
@open_loam_key_type ||= begin
|
|
39
39
|
requested = (options[:primary_key_type] || host_app_primary_key_type).to_s
|
|
40
40
|
requested = "bigint" if requested.empty?
|
|
41
41
|
|
|
42
42
|
unless SUPPORTED.include?(requested)
|
|
43
43
|
raise Thor::Error,
|
|
44
|
-
"Unsupported --primary-key-type #{requested.inspect}.
|
|
45
|
-
"A key type
|
|
44
|
+
"Unsupported --primary-key-type #{requested.inspect}. OpenLoam generates #{SUPPORTED.join(', ')}. " \
|
|
45
|
+
"A key type OpenLoam does not know how to size would produce migrations that look right and " \
|
|
46
46
|
"do not line up with your tables."
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -60,40 +60,40 @@ module Loam
|
|
|
60
60
|
nil
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def
|
|
63
|
+
def open_loam_key_limit
|
|
64
64
|
return options[:key_limit].to_i if options[:key_limit]
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
open_loam_key_type == :string ? DEFAULT_STRING_LIMIT : nil
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
# Appended to `create_table :x` — "" for bigint, so the default path is
|
|
70
70
|
# byte-for-byte what it was.
|
|
71
|
-
def
|
|
72
|
-
return "" if
|
|
71
|
+
def open_loam_id_option
|
|
72
|
+
return "" if open_loam_key_type == :bigint
|
|
73
73
|
|
|
74
|
-
"#{
|
|
74
|
+
"#{open_loam_key_fragment.sub('type:', 'id:')}"
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# Appended inside `t.references :x, ...`.
|
|
78
|
-
def
|
|
79
|
-
return "" if
|
|
78
|
+
def open_loam_type_option
|
|
79
|
+
return "" if open_loam_key_type == :bigint
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
open_loam_key_fragment
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# The bare column type for a polymorphic or un-constrained `*_id`, which
|
|
85
85
|
# cannot use t.references.
|
|
86
|
-
def
|
|
87
|
-
|
|
86
|
+
def open_loam_key_column_type
|
|
87
|
+
open_loam_key_type
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
def
|
|
91
|
-
|
|
90
|
+
def open_loam_key_limit_option
|
|
91
|
+
open_loam_key_limit ? ", limit: #{open_loam_key_limit}" : ""
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
def
|
|
95
|
-
fragment = ", type: :#{
|
|
96
|
-
fragment += ", limit: #{
|
|
94
|
+
def open_loam_key_fragment
|
|
95
|
+
fragment = ", type: :#{open_loam_key_type}"
|
|
96
|
+
fragment += ", limit: #{open_loam_key_limit}" if open_loam_key_limit
|
|
97
97
|
fragment
|
|
98
98
|
end
|
|
99
99
|
end
|
data/lib/open-loam.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Bundler auto-requires the gem's name ("open-loam") on load. The library's
|
|
2
|
-
# entry point and whole namespace are `
|
|
2
|
+
# entry point and whole namespace are `open_loam` / `OpenLoam::` — the gem is only
|
|
3
3
|
# *distributed* as open-loam (the plain `loam` name is taken on RubyGems). This
|
|
4
4
|
# shim bridges the two so `gem "open-loam"` works with the default require.
|
|
5
|
-
require_relative "
|
|
5
|
+
require_relative "open_loam"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Files on a record, via ActiveStorage:
|
|
3
3
|
#
|
|
4
4
|
# equipment.files.attach(io: File.open(path), filename: "manual.pdf")
|
|
5
5
|
# equipment.files.each { |file| url_for(file) }
|
|
6
6
|
#
|
|
7
|
-
# KNOWN BOUNDARY: ActiveStorage blobs live in global tables that
|
|
7
|
+
# KNOWN BOUNDARY: ActiveStorage blobs live in global tables that OpenLoam does
|
|
8
8
|
# NOT tenant-scope. The association is scoped (a record only ever lists its
|
|
9
9
|
# own files, and the record is tenant-scoped), but a signed blob URL is a
|
|
10
10
|
# bearer capability: whoever holds the link can fetch the file, with no
|
|
@@ -15,8 +15,8 @@ module Loam
|
|
|
15
15
|
|
|
16
16
|
included do
|
|
17
17
|
unless respond_to?(:has_many_attached)
|
|
18
|
-
raise
|
|
19
|
-
"#{name} includes
|
|
18
|
+
raise OpenLoam::Error,
|
|
19
|
+
"#{name} includes OpenLoam::Attachable but ActiveStorage is not available. " \
|
|
20
20
|
"Run `bin/rails active_storage:install && bin/rails db:migrate`, or remove the include " \
|
|
21
21
|
"(this app was probably generated with --skip-active-storage)."
|
|
22
22
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Audit-by-default. Included in every generated entity: create/update/destroy
|
|
3
|
-
# writes a
|
|
3
|
+
# writes a OpenLoam::AuditRecord tagged with tenant + actor + changeset. "Who
|
|
4
4
|
# changed the excavator's price, and when" is answered structurally, not by
|
|
5
5
|
# remembering to log.
|
|
6
6
|
module Auditable
|
|
@@ -9,54 +9,54 @@ module Loam
|
|
|
9
9
|
IGNORED_ATTRIBUTES = %w[updated_at created_at].freeze
|
|
10
10
|
|
|
11
11
|
included do
|
|
12
|
-
after_create {
|
|
13
|
-
after_update {
|
|
14
|
-
after_destroy {
|
|
12
|
+
after_create { open_loam_audit("create") }
|
|
13
|
+
after_update { open_loam_audit(open_loam_audit_action) if open_loam_audit_changes.any? }
|
|
14
|
+
after_destroy { open_loam_audit("destroy") }
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
|
-
# Relabel the audit that the save inside the block writes.
|
|
19
|
+
# Relabel the audit that the save inside the block writes. OpenLoam::SoftDeletable
|
|
20
20
|
# uses this to record a soft-delete (an UPDATE) as "soft_delete"/"restore"
|
|
21
21
|
# rather than the "update" it would otherwise be — one audit path, reused,
|
|
22
22
|
# not a second one to keep in sync.
|
|
23
|
-
def
|
|
24
|
-
previous = @
|
|
25
|
-
@
|
|
23
|
+
def open_loam_audit_as(action)
|
|
24
|
+
previous = @open_loam_audit_action
|
|
25
|
+
@open_loam_audit_action = action
|
|
26
26
|
yield
|
|
27
27
|
ensure
|
|
28
|
-
@
|
|
28
|
+
@open_loam_audit_action = previous
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def
|
|
32
|
-
@
|
|
31
|
+
def open_loam_audit_action
|
|
32
|
+
@open_loam_audit_action || "update"
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def
|
|
36
|
-
|
|
35
|
+
def open_loam_audit(action)
|
|
36
|
+
OpenLoam::AuditRecord.create!(
|
|
37
37
|
auditable_type: self.class.name,
|
|
38
38
|
auditable_id: id,
|
|
39
39
|
action: action,
|
|
40
|
-
actor_id:
|
|
41
|
-
changeset: action == "destroy" ? {} :
|
|
40
|
+
actor_id: OpenLoam::Current.actor&.id,
|
|
41
|
+
changeset: action == "destroy" ? {} : open_loam_audit_changes
|
|
42
42
|
)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def
|
|
45
|
+
def open_loam_audit_changes
|
|
46
46
|
changes = saved_changes.except(*IGNORED_ATTRIBUTES)
|
|
47
|
-
|
|
47
|
+
open_loam_redact_encrypted(changes)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# The audit trail must record the FACT that an encrypted field changed, never
|
|
51
51
|
# its value — neither the plaintext nor the ciphertext (a ciphertext still
|
|
52
52
|
# leaks length and, over time, correlations). So an encrypted column's change
|
|
53
53
|
# becomes "[encrypted]", and its blind-index sibling is dropped entirely.
|
|
54
|
-
# A no-op for models without
|
|
55
|
-
def
|
|
56
|
-
return changes unless self.class.respond_to?(:
|
|
54
|
+
# A no-op for models without OpenLoam::Encryptable.
|
|
55
|
+
def open_loam_redact_encrypted(changes)
|
|
56
|
+
return changes unless self.class.respond_to?(:open_loam_encrypted_attributes)
|
|
57
57
|
|
|
58
|
-
encrypted = self.class.
|
|
59
|
-
hash_columns = self.class.
|
|
58
|
+
encrypted = self.class.open_loam_encrypted_attributes.map(&:to_s)
|
|
59
|
+
hash_columns = self.class.open_loam_searchable_encrypted_attributes.map { |name| "#{name}_hash" }
|
|
60
60
|
|
|
61
61
|
changes.each_with_object({}) do |(column, values), redacted|
|
|
62
62
|
next if hash_columns.include?(column)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Rate-limiting + lockout for authentication, to blunt online brute-force of
|
|
3
3
|
# passwords and (especially) 6-digit TOTP codes. A DB-backed counter keyed by
|
|
4
4
|
# the submitted identifier: after N failures within a window, the identifier is
|
|
5
5
|
# locked; a success clears the counter.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# OpenLoam::AuthThrottle.locked?(email) # refuse if true
|
|
8
|
+
# OpenLoam::AuthThrottle.record_failure(email, kind: "password", ip: request.ip)
|
|
9
|
+
# OpenLoam::AuthThrottle.clear(email, kind: "password") # on success
|
|
10
10
|
#
|
|
11
11
|
# PER-IDENTIFIER is the primary defense (an attacker targets one account /
|
|
12
12
|
# code). A per-ip throttle to blunt spraying across accounts is a clean
|
|
@@ -15,7 +15,7 @@ module Loam
|
|
|
15
15
|
# (needs a cache store + a gem); this DB counter is the portable,
|
|
16
16
|
# single-process-correct prototype.
|
|
17
17
|
#
|
|
18
|
-
# Thresholds come from
|
|
18
|
+
# Thresholds come from OpenLoam::Configs (per-tenant-overridable, but read globally
|
|
19
19
|
# here at the auth layer with sane defaults):
|
|
20
20
|
# security.max_auth_attempts (default 10)
|
|
21
21
|
# security.auth_window_minutes (default 15) — failures counted in this window
|
|
@@ -24,15 +24,15 @@ module Loam
|
|
|
24
24
|
module_function
|
|
25
25
|
|
|
26
26
|
def max_attempts
|
|
27
|
-
|
|
27
|
+
OpenLoam::Configs.get("security.max_auth_attempts", default: 10).to_i
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def window
|
|
31
|
-
|
|
31
|
+
OpenLoam::Configs.get("security.auth_window_minutes", default: 15).to_i.minutes
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def lockout
|
|
35
|
-
|
|
35
|
+
OpenLoam::Configs.get("security.auth_lockout_minutes", default: 15).to_i.minutes
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Log a failed attempt. Recorded for ANY submitted identifier — existing or
|
|
@@ -41,11 +41,14 @@ module Loam
|
|
|
41
41
|
identifier = normalize(identifier)
|
|
42
42
|
return if identifier.blank?
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
OpenLoam::AuthAttempt.create!(identifier: identifier, kind: kind.to_s, ip: ip)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# Locked if there are >= max failures within the window. Old attempts age out
|
|
48
48
|
# of the window automatically (the window query IS the expiry — no reaper).
|
|
49
|
+
#
|
|
50
|
+
# Both auth call sites ask WITHOUT a kind on purpose: failures on either
|
|
51
|
+
# factor lock both, so grinding TOTP also costs the attacker the password form.
|
|
49
52
|
def locked?(identifier, kind: nil)
|
|
50
53
|
recent_failures(identifier, kind: kind) >= max_attempts
|
|
51
54
|
end
|
|
@@ -58,8 +61,13 @@ module Loam
|
|
|
58
61
|
|
|
59
62
|
# Reset the counter — call on a SUCCESSFUL auth so a legitimate user who
|
|
60
63
|
# eventually gets in isn't left throttled.
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
#
|
|
65
|
+
# Pass `kind:` — clearing every kind on one factor's success lets an attacker
|
|
66
|
+
# holding the password reset the TOTP counter before each guess.
|
|
67
|
+
def clear(identifier, kind: nil)
|
|
68
|
+
scope = attempts(identifier)
|
|
69
|
+
scope = scope.where(kind: Array(kind).map(&:to_s)) if kind
|
|
70
|
+
scope.delete_all
|
|
63
71
|
end
|
|
64
72
|
|
|
65
73
|
# Seconds until the identifier unlocks (for the "try again in N" message),
|
|
@@ -73,7 +81,7 @@ module Loam
|
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
def attempts(identifier)
|
|
76
|
-
|
|
84
|
+
OpenLoam::AuthAttempt.where(identifier: normalize(identifier))
|
|
77
85
|
end
|
|
78
86
|
|
|
79
87
|
def normalize(identifier)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Datatable bulk actions over selected records — policy-checked PER record and
|
|
3
3
|
# tenant-scoped. The ids are resolved THROUGH the tenant scope
|
|
4
4
|
# (`model.where(id: ids)`), so a forged cross-tenant id is simply not found —
|
|
5
5
|
# a bulk op can never reach another tenant's rows.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# OpenLoam::Bulk.soft_delete(Equipment, params[:ids])
|
|
8
|
+
# OpenLoam::Bulk.set_field(Equipment, params[:ids], field: "status", value: "retired")
|
|
9
9
|
#
|
|
10
10
|
# Returns the number of records actually affected (a record the actor may not
|
|
11
11
|
# touch, or an id from another tenant, is silently skipped).
|
|
@@ -32,7 +32,7 @@ module Loam
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# A relation for "export selected" — tenant-scoped, so it composes with
|
|
35
|
-
#
|
|
35
|
+
# OpenLoam::Export.csv and can't leak another tenant's rows.
|
|
36
36
|
def selected(model, ids)
|
|
37
37
|
model.where(id: Array(ids))
|
|
38
38
|
end
|
|
@@ -40,20 +40,20 @@ module Loam
|
|
|
40
40
|
def each_permitted(model, ids)
|
|
41
41
|
count = 0
|
|
42
42
|
selected(model, ids).find_each do |record|
|
|
43
|
-
count += 1 if yield(record,
|
|
43
|
+
count += 1 if yield(record, OpenLoam::Policy.for(record))
|
|
44
44
|
end
|
|
45
45
|
count
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def writable_column?(model, field)
|
|
49
|
-
model.column_names.include?(field) &&
|
|
49
|
+
model.column_names.include?(field) && OpenLoam::Import::PLUMBING.exclude?(field) && !workflow_column?(model, field)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# A workflow status column may ONLY change through a transition (role-gated).
|
|
53
53
|
# Skip it here so a bulk set-field can't self-"approve" (the model guard is
|
|
54
54
|
# the backstop; this keeps the bulk op a clean no-op rather than an error).
|
|
55
55
|
def workflow_column?(model, field)
|
|
56
|
-
model.respond_to?(:
|
|
56
|
+
model.respond_to?(:open_loam_workflow) && model.open_loam_workflow&.column == field
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
module BusinessRules
|
|
3
3
|
# The typed action vocabulary — a FIXED, safe set the engine dispatches. No
|
|
4
4
|
# arbitrary code: an action is a json descriptor with a `type` and known
|
|
@@ -9,7 +9,7 @@ module Loam
|
|
|
9
9
|
|
|
10
10
|
# Run one action against the record that triggered the rule. Returns a
|
|
11
11
|
# short label of what it did (for the execution log). `:veto` is the
|
|
12
|
-
# block_transition signal (see
|
|
12
|
+
# block_transition signal (see OpenLoam::BusinessRules.veto?).
|
|
13
13
|
def self.run(action, record)
|
|
14
14
|
case action["type"].to_s
|
|
15
15
|
when "notify" then run_notify(action, record)
|
|
@@ -24,10 +24,10 @@ module Loam
|
|
|
24
24
|
title = action["title"].to_s
|
|
25
25
|
body = action["body"]
|
|
26
26
|
if action["role"].present?
|
|
27
|
-
|
|
27
|
+
OpenLoam::Notifications.notify_role(action["role"], title: title, body: body)
|
|
28
28
|
elsif action["user_id"].present?
|
|
29
29
|
user = User.find_by(id: action["user_id"])
|
|
30
|
-
|
|
30
|
+
OpenLoam::Notifications.notify(user, title: title, body: body) if user
|
|
31
31
|
end
|
|
32
32
|
"notify"
|
|
33
33
|
end
|
|
@@ -35,7 +35,7 @@ module Loam
|
|
|
35
35
|
# Publish validates the name format (raising InvalidEventNameError), so a
|
|
36
36
|
# malformed event name is caught by the engine's per-rule isolation.
|
|
37
37
|
def self.run_emit_event(action)
|
|
38
|
-
|
|
38
|
+
OpenLoam::Events.publish(action["name"], (action["payload"] || {}).symbolize_keys)
|
|
39
39
|
"emit_event(#{action["name"]})"
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -65,7 +65,7 @@ module Loam
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def self.refuse_workflow_column!(record, field)
|
|
68
|
-
return unless record.class.respond_to?(:
|
|
68
|
+
return unless record.class.respond_to?(:open_loam_workflow) && record.class.open_loam_workflow&.column == field
|
|
69
69
|
|
|
70
70
|
raise ArgumentError, "set_field must not write the workflow column #{field.inspect} — use a transition"
|
|
71
71
|
end
|
|
@@ -83,7 +83,7 @@ module Loam
|
|
|
83
83
|
# but never write one whose tenant is not the tenant in context.
|
|
84
84
|
def self.refuse_cross_tenant!(record)
|
|
85
85
|
return unless record.respond_to?(:tenant_id)
|
|
86
|
-
return if record.tenant_id ==
|
|
86
|
+
return if record.tenant_id == OpenLoam.tenant!.id
|
|
87
87
|
|
|
88
88
|
raise ArgumentError, "set_field refuses a record outside the current tenant"
|
|
89
89
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
module BusinessRules
|
|
3
3
|
# The SAFE condition evaluator — the critical security boundary of the rules
|
|
4
4
|
# engine. A rule is DATA an admin edits, so this must NEVER run arbitrary
|
|
@@ -8,7 +8,7 @@ module Loam
|
|
|
8
8
|
# else is refused — no `send` of arbitrary methods, no SQL, no eval.
|
|
9
9
|
module Condition
|
|
10
10
|
OPS = %w[eq neq gt gte lt lte in contains present blank].freeze
|
|
11
|
-
REFUSED = :
|
|
11
|
+
REFUSED = :__open_loam_field_refused__
|
|
12
12
|
|
|
13
13
|
def self.matches?(node, record)
|
|
14
14
|
node = node.is_a?(Hash) ? node : {}
|
|
@@ -48,7 +48,7 @@ module Loam
|
|
|
48
48
|
elsif custom_field?(record, field)
|
|
49
49
|
begin
|
|
50
50
|
record.custom_field(field)
|
|
51
|
-
rescue
|
|
51
|
+
rescue OpenLoam::UnknownCustomFieldError
|
|
52
52
|
REFUSED
|
|
53
53
|
end
|
|
54
54
|
else
|
|
@@ -59,8 +59,8 @@ module Loam
|
|
|
59
59
|
def self.refused_column?(klass, field)
|
|
60
60
|
return true if field == "tenant_id"
|
|
61
61
|
|
|
62
|
-
klass.respond_to?(:
|
|
63
|
-
klass.
|
|
62
|
+
klass.respond_to?(:open_loam_encrypted_attributes) &&
|
|
63
|
+
klass.open_loam_encrypted_attributes.map(&:to_s).include?(field)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def self.custom_field?(record, field)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
require "
|
|
2
|
-
require "
|
|
1
|
+
require "open_loam/business_rules/condition"
|
|
2
|
+
require "open_loam/business_rules/actions"
|
|
3
3
|
|
|
4
|
-
module
|
|
5
|
-
# The engine that evaluates
|
|
4
|
+
module OpenLoam
|
|
5
|
+
# The engine that evaluates OpenLoam::BusinessRule rows: WHEN a trigger fires and
|
|
6
6
|
# the condition holds, THEN run the actions — in the event's tenant context,
|
|
7
7
|
# in priority order, each rule isolated so one failure never breaks dispatch.
|
|
8
8
|
#
|
|
9
|
-
# It listens to every
|
|
9
|
+
# It listens to every OpenLoam event (like OpenLoam::Webhooks), so a tenant can add a
|
|
10
10
|
# rule from the admin without a reboot. Evaluation runs INLINE in whatever
|
|
11
11
|
# published the event — cheap for the prototype; a job-based dispatcher is the
|
|
12
12
|
# scaling path.
|
|
@@ -15,19 +15,19 @@ module Loam
|
|
|
15
15
|
# rules, but only so deep, so a self-triggering rule terminates.
|
|
16
16
|
|
|
17
17
|
class << self
|
|
18
|
-
# Wired once from
|
|
19
|
-
# every rule twice (see
|
|
18
|
+
# Wired once from OpenLoam::Engine. Idempotent — subscribing twice would run
|
|
19
|
+
# every rule twice (see OpenLoam::Webhooks.subscribe!).
|
|
20
20
|
def subscribe!
|
|
21
|
-
@subscription ||=
|
|
21
|
+
@subscription ||= OpenLoam::Events.subscribe_all { |event_name, payload| on_event(event_name, payload) }
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def on_event(event_name, payload)
|
|
25
|
-
tenant =
|
|
25
|
+
tenant = OpenLoam::Tenant.find_by(id: payload[:tenant_id])
|
|
26
26
|
return if tenant.nil?
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
OpenLoam.as_tenant(tenant) do
|
|
29
29
|
within_depth_guard do
|
|
30
|
-
|
|
30
|
+
OpenLoam::BusinessRule.active.by_priority.each do |rule|
|
|
31
31
|
next unless rule.matches_trigger?(event_name)
|
|
32
32
|
|
|
33
33
|
run_rule(rule, subject_for(rule, payload), event_name)
|
|
@@ -40,7 +40,7 @@ module Loam
|
|
|
40
40
|
# active rules for the record's entity type (optionally filtered to a
|
|
41
41
|
# trigger) in priority order.
|
|
42
42
|
def evaluate(record, trigger: nil)
|
|
43
|
-
|
|
43
|
+
OpenLoam::BusinessRule.active.for_entity(record.class.base_class.name).by_priority.each do |rule|
|
|
44
44
|
next if trigger && !rule.matches_trigger?(trigger)
|
|
45
45
|
|
|
46
46
|
run_rule(rule, record, trigger)
|
|
@@ -51,7 +51,7 @@ module Loam
|
|
|
51
51
|
# holds, veto this transition? Consulted by a workflow BEFORE it moves (see
|
|
52
52
|
# the demo's DamageReport). Evaluate-only — no actions run, nothing logged.
|
|
53
53
|
def veto?(record, trigger)
|
|
54
|
-
|
|
54
|
+
OpenLoam::BusinessRule.active.for_entity(record.class.base_class.name).by_priority.any? do |rule|
|
|
55
55
|
rule.matches_trigger?(trigger) &&
|
|
56
56
|
rule.action_list.any? { |action| action["type"].to_s == "block_transition" } &&
|
|
57
57
|
Condition.matches?(rule.condition_tree, record)
|
|
@@ -81,7 +81,7 @@ module Loam
|
|
|
81
81
|
# deterministic "condition didn't match" ones (readable from the rule).
|
|
82
82
|
return if !matched && error.nil?
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
OpenLoam::BusinessRuleRun.create!(
|
|
85
85
|
business_rule: rule,
|
|
86
86
|
subject_type: record&.class&.base_class&.name,
|
|
87
87
|
subject_id: record&.id,
|
|
@@ -102,21 +102,21 @@ module Loam
|
|
|
102
102
|
# A rule may ONLY target a tenant-scoped model. This is the security
|
|
103
103
|
# boundary: a global model like `User` has no tenant default scope, so a
|
|
104
104
|
# find_by(id:) would reach across tenants — refusing anything that is not
|
|
105
|
-
# a
|
|
105
|
+
# a OpenLoam::TenantRecord closes that, and a TenantRecord.find_by can only
|
|
106
106
|
# ever return a record in the CURRENT tenant even if an id is injected.
|
|
107
|
-
return nil unless klass.is_a?(Class) && klass <
|
|
107
|
+
return nil unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord
|
|
108
108
|
|
|
109
109
|
klass.find_by(id: payload[:id])
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def within_depth_guard
|
|
113
|
-
depth = Thread.current[:
|
|
113
|
+
depth = Thread.current[:open_loam_rule_depth] ||= 0
|
|
114
114
|
return if depth >= MAX_DEPTH
|
|
115
115
|
|
|
116
|
-
Thread.current[:
|
|
116
|
+
Thread.current[:open_loam_rule_depth] = depth + 1
|
|
117
117
|
yield
|
|
118
118
|
ensure
|
|
119
|
-
Thread.current[:
|
|
119
|
+
Thread.current[:open_loam_rule_depth] = depth if depth
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
122
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Discussion on a record:
|
|
3
3
|
#
|
|
4
|
-
# equipment.comment!("Hydraulics serviced") # as
|
|
5
|
-
# equipment.
|
|
4
|
+
# equipment.comment!("Hydraulics serviced") # as OpenLoam.actor
|
|
5
|
+
# equipment.open_loam_comments.oldest_first
|
|
6
6
|
#
|
|
7
7
|
# Included by every generated entity. Comments are tenant-scoped like their
|
|
8
8
|
# subject, so a discussion can never be read from another tenant.
|
|
@@ -10,13 +10,13 @@ module Loam
|
|
|
10
10
|
extend ActiveSupport::Concern
|
|
11
11
|
|
|
12
12
|
included do
|
|
13
|
-
has_many :
|
|
13
|
+
has_many :open_loam_comments, as: :commentable, class_name: "OpenLoam::Comment", dependent: :destroy
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
# Author defaults to whoever is acting — the same person the audit trail
|
|
17
17
|
# and the event payload would name.
|
|
18
|
-
def comment!(body, author:
|
|
19
|
-
|
|
18
|
+
def comment!(body, author: OpenLoam::Current.actor)
|
|
19
|
+
open_loam_comments.create!(body: body, author: author)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|