open-loam 0.1.0 → 0.2.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 +48 -8
- data/LICENSE +1 -1
- data/README.md +24 -24
- 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/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
- data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +7 -7
- data/app/models/{loam → open_loam}/api_token.rb +7 -7
- 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/{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 +23 -23
- 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/{loam → open_loam}/sso_provider.rb +8 -8
- data/app/models/open_loam/tenant.rb +20 -0
- data/app/models/{loam → open_loam}/translation.rb +5 -5
- data/app/models/{loam → open_loam}/webhook_endpoint.rb +6 -6
- 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 +2 -2
- data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
- data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +1 -1
- data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +21 -21
- 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 +4 -4
- 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 +90 -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 +36 -0
- data/lib/generators/{loam → open_loam}/install/install_generator.rb +47 -47
- data/lib/generators/open_loam/install/templates/AGENTS.md +341 -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/{loam → open_loam}/install/templates/admin/api_tokens_controller.rb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +38 -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 +10 -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 +5 -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/{loam → open_loam}/install/templates/admin/history_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +12 -12
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -2
- 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 +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +3 -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 +7 -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 +19 -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 +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +32 -32
- 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/sudo_controller.rb +5 -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/{loam → open_loam}/install/templates/api_base_controller.rb +15 -15
- data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
- data/lib/generators/{loam → open_loam}/install/templates/import_job.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +74 -74
- 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/{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 -9
- 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} +12 -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 +10 -10
- 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 +23 -23
- 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 +33 -33
- data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
- data/lib/{loam → open_loam}/encryption/key_provider.rb +3 -3
- data/lib/{loam → open_loam}/encryption.rb +11 -11
- data/lib/open_loam/engine.rb +57 -0
- data/lib/{loam → open_loam}/enrichers.rb +5 -5
- data/lib/{loam → open_loam}/errors.rb +10 -10
- data/lib/{loam → open_loam}/eval.rb +2 -2
- 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 +8 -8
- 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 -12
- data/lib/{loam → open_loam}/inbound_webhooks.rb +7 -7
- data/lib/{loam → open_loam}/lifecycle.rb +25 -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 +21 -21
- data/lib/{loam → open_loam}/notifications.rb +5 -5
- data/lib/{loam → open_loam}/open_api.rb +16 -16
- 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 +6 -6
- 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 +15 -15
- 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/{loam → open_loam}/sso/http_client.rb +3 -3
- data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
- data/lib/{loam → open_loam}/sso.rb +17 -17
- 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 +122 -0
- data/lib/tasks/{loam.rake → open_loam.rake} +43 -43
- data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
- data/lib/tasks/open_loam_mcp.rake +20 -0
- metadata +242 -242
- data/app/models/loam/dictionary.rb +0 -20
- data/app/models/loam/search_token.rb +0 -9
- data/app/models/loam/tenant.rb +0 -20
- 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/dashboard_controller.rb +0 -10
- 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/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/version.rb +0 -3
- data/lib/loam.rb +0 -122
- data/lib/tasks/loam_mcp.rake +0 -20
- /data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +0 -0
- /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_index.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,16 +1,16 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A read-model projection of ONE custom field of ONE record into typed,
|
|
3
3
|
# INDEXED columns — so filtering/sorting/searching on a custom field is
|
|
4
4
|
# index-backed instead of a per-row JSON extraction over the `custom_fields`
|
|
5
5
|
# column (which can't be indexed and full-scans at scale). One row per
|
|
6
|
-
# (record, custom field); maintained by
|
|
7
|
-
#
|
|
6
|
+
# (record, custom field); maintained by OpenLoam::CustomFieldIndex from the
|
|
7
|
+
# OpenLoam::CustomFields save/destroy hooks. Tenant-scoped.
|
|
8
8
|
#
|
|
9
9
|
# The value is written into the column matching the field's declared type
|
|
10
10
|
# (value_number / value_boolean / value_datetime) AND always into value_text
|
|
11
11
|
# (its string form) for text ops (contains/present) and canonical equality.
|
|
12
|
-
class CustomFieldValue <
|
|
13
|
-
self.table_name = "
|
|
12
|
+
class CustomFieldValue < OpenLoam::TenantRecord
|
|
13
|
+
self.table_name = "open_loam_custom_field_values"
|
|
14
14
|
|
|
15
15
|
validates :indexable_type, :indexable_id, :field_key, presence: true
|
|
16
16
|
validates :field_key, uniqueness: { scope: %i[indexable_type indexable_id] }
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A tenant's dashboard layout: which registered widgets appear, in what order.
|
|
3
3
|
# A manager arranges these on the Dashboard settings screen; the dashboard
|
|
4
|
-
# renders the active ones by position (see
|
|
4
|
+
# renders the active ones by position (see OpenLoam::Dashboard). Tenant-scoped and
|
|
5
5
|
# audited.
|
|
6
|
-
class DashboardWidget <
|
|
7
|
-
self.table_name = "
|
|
6
|
+
class DashboardWidget < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_dashboard_widgets"
|
|
8
8
|
|
|
9
|
-
include
|
|
9
|
+
include OpenLoam::Auditable
|
|
10
10
|
|
|
11
11
|
validates :widget_key, presence: true, uniqueness: { scope: :tenant_id }
|
|
12
12
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# A per-tenant managed lookup list — a named set of entries (e.g.
|
|
3
|
+
# "damage_severity" → minor/major/critical). Admins curate the values without a
|
|
4
|
+
# code deploy, and a OpenLoam::FieldDefinition of type "dictionary" can point a
|
|
5
|
+
# custom field at one. Tenant-scoped and audited like every OpenLoam entity; see
|
|
6
|
+
# OpenLoam::Dictionaries for the read API.
|
|
7
|
+
class Dictionary < OpenLoam::TenantRecord
|
|
8
|
+
self.table_name = "open_loam_dictionaries"
|
|
9
|
+
|
|
10
|
+
include OpenLoam::Auditable
|
|
11
|
+
|
|
12
|
+
has_many :entries, class_name: "OpenLoam::DictionaryEntry",
|
|
13
|
+
foreign_key: :dictionary_id, dependent: :delete_all
|
|
14
|
+
|
|
15
|
+
validates :key, presence: true, uniqueness: { scope: :tenant_id }
|
|
16
|
+
validates :name, presence: true
|
|
17
|
+
|
|
18
|
+
normalizes :key, with: ->(key) { key.to_s.strip.presence }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
module
|
|
2
|
-
# One option in a
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# One option in a OpenLoam::Dictionary: a stored `value` (the code that lands in a
|
|
3
3
|
# record) plus display metadata (label, color, icon), an ordering `position`,
|
|
4
4
|
# a `is_default` flag, and an `active` switch (a retired option stops being
|
|
5
5
|
# offered without deleting the historical values already stored). Tenant-scoped
|
|
6
6
|
# and audited.
|
|
7
|
-
class DictionaryEntry <
|
|
8
|
-
self.table_name = "
|
|
7
|
+
class DictionaryEntry < OpenLoam::TenantRecord
|
|
8
|
+
self.table_name = "open_loam_dictionary_entries"
|
|
9
9
|
|
|
10
|
-
include
|
|
10
|
+
include OpenLoam::Auditable
|
|
11
11
|
|
|
12
|
-
belongs_to :dictionary, class_name: "
|
|
12
|
+
belongs_to :dictionary, class_name: "OpenLoam::Dictionary"
|
|
13
13
|
|
|
14
14
|
validates :value, presence: true, uniqueness: { scope: :dictionary_id }
|
|
15
15
|
validates :label, presence: true
|
|
@@ -25,7 +25,7 @@ module Loam
|
|
|
25
25
|
# A within-request edit must not read a stale cached list (the cache also
|
|
26
26
|
# resets per request, so this only matters when a write and a read share one).
|
|
27
27
|
def clear_dictionary_cache
|
|
28
|
-
|
|
28
|
+
OpenLoam::Dictionaries.clear_cache
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# One durable delivery of one event to one persistent subscriber
|
|
3
|
-
# (see
|
|
3
|
+
# (see OpenLoam::DurableEvents). The ROW is the durable record of intent: it is
|
|
4
4
|
# committed in the event's tenant at publish time, and the background job is
|
|
5
5
|
# only an accelerator. If the job is lost, the sweep re-enqueues from this row.
|
|
6
6
|
#
|
|
7
7
|
# Lifecycle: pending -> delivered (handler ran) | dead (handler removed, or
|
|
8
8
|
# MAX_ATTEMPTS exhausted). A pending row with next_attempt_at in the future is
|
|
9
9
|
# simply waiting out its backoff.
|
|
10
|
-
class EventDelivery <
|
|
11
|
-
self.table_name = "
|
|
10
|
+
class EventDelivery < OpenLoam::TenantRecord
|
|
11
|
+
self.table_name = "open_loam_event_deliveries"
|
|
12
12
|
|
|
13
13
|
STATUSES = %w[pending delivered dead].freeze
|
|
14
14
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Runtime declaration of a migration-free field on a tenant-scoped entity —
|
|
3
3
|
# "entity_type Equipment gets a field called serial_number of type string,
|
|
4
4
|
# writable by managers only." Tenant-scoped like everything else: a field
|
|
5
5
|
# defined in one tenant is invisible to another's records of the same
|
|
6
6
|
# entity_type, and definitions are the ONLY way values in an entity's
|
|
7
|
-
# `custom_fields` json column get read/written (see
|
|
8
|
-
class FieldDefinition <
|
|
9
|
-
self.table_name = "
|
|
7
|
+
# `custom_fields` json column get read/written (see OpenLoam::CustomFields).
|
|
8
|
+
class FieldDefinition < OpenLoam::TenantRecord
|
|
9
|
+
self.table_name = "open_loam_field_definitions"
|
|
10
10
|
|
|
11
11
|
FIELD_TYPES = %w[string text integer decimal boolean date datetime dictionary].freeze
|
|
12
12
|
|
|
@@ -17,15 +17,15 @@ module Loam
|
|
|
17
17
|
|
|
18
18
|
# Field-level READ gate for the runtime field, mirroring writable_roles: an
|
|
19
19
|
# empty readable_roles means any member may read it; otherwise only the listed
|
|
20
|
-
# roles. Used by
|
|
21
|
-
# restricted field) and
|
|
20
|
+
# roles. Used by OpenLoam::CustomFieldIndex (so a filter can't be an oracle on a
|
|
21
|
+
# restricted field) and OpenLoam::Policy#custom_field_readable?.
|
|
22
22
|
def readable_by?(role)
|
|
23
23
|
roles = readable_roles
|
|
24
24
|
roles.blank? || (role.present? && roles.map(&:to_sym).include?(role.to_sym))
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# The `config` json holds type-specific settings. For a "dictionary" field it
|
|
28
|
-
# carries the key of the
|
|
28
|
+
# carries the key of the OpenLoam::Dictionary whose entries populate the select.
|
|
29
29
|
def dictionary_key
|
|
30
30
|
config.is_a?(Hash) ? config["dictionary"] : nil
|
|
31
31
|
end
|
|
@@ -40,7 +40,7 @@ module Loam
|
|
|
40
40
|
def dictionary_key_resolves
|
|
41
41
|
if dictionary_key.blank?
|
|
42
42
|
errors.add(:dictionary_key, "is required for a dictionary field")
|
|
43
|
-
elsif
|
|
43
|
+
elsif OpenLoam::Dictionaries.get(dictionary_key).nil?
|
|
44
44
|
errors.add(:dictionary_key, "must name an existing dictionary in this tenant")
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# One received inbound webhook. Two jobs: an idempotency ledger (a unique
|
|
3
3
|
# (source_id, external_id) index turns a replayed delivery into a no-op) and an
|
|
4
4
|
# audit/payload store — the raw body is kept here so an event subscriber reads
|
|
5
5
|
# it, keeping the published event payload scalar-clean (ids only).
|
|
6
|
-
class InboundWebhookDelivery <
|
|
7
|
-
self.table_name = "
|
|
6
|
+
class InboundWebhookDelivery < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_inbound_webhook_deliveries"
|
|
8
8
|
|
|
9
|
-
belongs_to :source, class_name: "
|
|
9
|
+
belongs_to :source, class_name: "OpenLoam::InboundWebhookSource", inverse_of: :deliveries
|
|
10
10
|
|
|
11
11
|
validates :external_id, presence: true
|
|
12
12
|
# (source_id, external_id) uniqueness is enforced by a DB index — the ledger
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A registered external system allowed to POST webhooks INTO this tenant — the
|
|
3
|
-
# inbound sibling of
|
|
3
|
+
# inbound sibling of OpenLoam::WebhookEndpoint (which delivers events OUT). The
|
|
4
4
|
# `token` is the unguessable URL id (`/webhooks/:token`) that identifies the
|
|
5
5
|
# source; the `secret` is the HMAC key that AUTHENTICATES each call. Identity is
|
|
6
6
|
# not authority: rotating either is supported from the admin.
|
|
7
7
|
#
|
|
8
|
-
# On a verified delivery,
|
|
9
|
-
# with a reference to the stored
|
|
10
|
-
# subscribers (
|
|
11
|
-
class InboundWebhookSource <
|
|
12
|
-
self.table_name = "
|
|
8
|
+
# On a verified delivery, OpenLoam publishes `event_name` onto the domain event bus
|
|
9
|
+
# with a reference to the stored OpenLoam::InboundWebhookDelivery, so durable
|
|
10
|
+
# subscribers (OpenLoam::DurableEvents) react — the payload itself lives on the row.
|
|
11
|
+
class InboundWebhookSource < OpenLoam::TenantRecord
|
|
12
|
+
self.table_name = "open_loam_inbound_webhook_sources"
|
|
13
13
|
|
|
14
|
-
DEFAULT_SIGNATURE_HEADER = "X-
|
|
14
|
+
DEFAULT_SIGNATURE_HEADER = "X-OpenLoam-Signature".freeze
|
|
15
15
|
DEFAULT_TOLERANCE = 300 # seconds
|
|
16
16
|
|
|
17
|
-
has_many :deliveries, class_name: "
|
|
17
|
+
has_many :deliveries, class_name: "OpenLoam::InboundWebhookDelivery",
|
|
18
18
|
foreign_key: :source_id, dependent: :destroy, inverse_of: :source
|
|
19
19
|
|
|
20
20
|
validates :name, presence: true
|
|
21
21
|
validates :token, presence: true, uniqueness: true
|
|
22
22
|
validates :secret, presence: true
|
|
23
23
|
validates :event_name, presence: true,
|
|
24
|
-
format: { with:
|
|
24
|
+
format: { with: OpenLoam::Events::NAME_FORMAT, message: "must follow domain.thing.happened" }
|
|
25
25
|
|
|
26
26
|
scope :active, -> { where(active: true) }
|
|
27
27
|
|
|
@@ -32,9 +32,9 @@ module Loam
|
|
|
32
32
|
self.active = true if active.nil?
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# THE blessed cross-tenant lookup (see
|
|
35
|
+
# THE blessed cross-tenant lookup (see OpenLoam::ApiToken.authenticate): a public
|
|
36
36
|
# inbound request arrives with no tenant context — the token in the URL is how
|
|
37
|
-
# it discovers its tenant. Establishes
|
|
37
|
+
# it discovers its tenant. Establishes OpenLoam::Current.tenant (never an actor:
|
|
38
38
|
# the sender is a machine, not a user) and returns the ACTIVE source, or nil.
|
|
39
39
|
def self.resolve(raw_token)
|
|
40
40
|
return nil if raw_token.blank?
|
|
@@ -42,7 +42,7 @@ module Loam
|
|
|
42
42
|
source = unscoped.find_by(token: raw_token)
|
|
43
43
|
return nil unless source&.active?
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
OpenLoam::Current.tenant = source.tenant
|
|
46
46
|
source
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Connects an actor (the host app's User) to a tenant with a role. Roles are
|
|
3
3
|
# plain strings ("manager", "employee", ...) — policies interpret them.
|
|
4
4
|
# Tenant-scoped like everything else: asking for someone's role always means
|
|
5
5
|
# "their role in the CURRENT tenant".
|
|
6
|
-
class Membership <
|
|
7
|
-
self.table_name = "
|
|
6
|
+
class Membership < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_memberships"
|
|
8
8
|
|
|
9
9
|
belongs_to :user
|
|
10
10
|
|
|
11
11
|
validates :role, presence: true
|
|
12
12
|
validates :user_id, uniqueness: { scope: :tenant_id }
|
|
13
13
|
|
|
14
|
-
# The other blessed cross-tenant lookup (see
|
|
14
|
+
# The other blessed cross-tenant lookup (see OpenLoam::ApiToken.authenticate).
|
|
15
15
|
# "Which tenants may this person enter?" is asked at login, before any
|
|
16
16
|
# tenant is chosen, so it cannot be answered from inside one — which is why
|
|
17
17
|
# it lives in the gem rather than in host app code, where reaching across
|
|
18
18
|
# tenants is a guardrail failure.
|
|
19
19
|
#
|
|
20
|
-
# Returns a
|
|
20
|
+
# Returns a OpenLoam::Tenant relation, so callers can order/filter it further.
|
|
21
21
|
def self.tenants_for(user)
|
|
22
22
|
user_id = user.respond_to?(:id) ? user.id : user
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
OpenLoam::Tenant.where(id: unscoped.where(user_id: user_id).select(:tenant_id)).order(:name)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
require "bcrypt"
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module OpenLoam
|
|
4
4
|
# A user's multi-factor credential: a TOTP secret plus single-use recovery
|
|
5
5
|
# codes. Deliberately NOT tenant-scoped — MFA belongs to the PERSON, who spans
|
|
6
6
|
# tenants, and the second-factor challenge runs at login BEFORE any tenant is
|
|
7
|
-
# chosen. So the secret is encrypted under a USER-scoped key (
|
|
7
|
+
# chosen. So the secret is encrypted under a USER-scoped key (OpenLoam::Encryptable
|
|
8
8
|
# `scope:`), which decrypts in any tenant and with no tenant at all — the whole
|
|
9
9
|
# reason a per-tenant key would be a lockout bug here.
|
|
10
10
|
class MfaCredential < ApplicationRecord
|
|
11
|
-
include
|
|
12
|
-
self.table_name = "
|
|
11
|
+
include OpenLoam::GeneratedKey
|
|
12
|
+
self.table_name = "open_loam_mfa_credentials"
|
|
13
13
|
|
|
14
14
|
RECOVERY_CODE_COUNT = 10
|
|
15
15
|
|
|
16
16
|
belongs_to :user
|
|
17
17
|
|
|
18
|
-
include
|
|
18
|
+
include OpenLoam::Encryptable
|
|
19
19
|
encrypts :totp_secret, scope: ->(credential) { "user/#{credential.user_id}" }
|
|
20
20
|
|
|
21
21
|
# Recovery codes are stored HASHED (BCrypt), never in the clear: [{ "digest",
|
|
@@ -46,7 +46,7 @@ module Loam
|
|
|
46
46
|
# if the code is wrong. The old secret stays valid until this succeeds, so a
|
|
47
47
|
# half-finished re-enrollment never downgrades an active credential.
|
|
48
48
|
def activate_with!(candidate_secret, code)
|
|
49
|
-
step =
|
|
49
|
+
step = OpenLoam::Totp.matching_step(candidate_secret, code)
|
|
50
50
|
return nil unless step
|
|
51
51
|
|
|
52
52
|
self.totp_secret = candidate_secret
|
|
@@ -66,7 +66,7 @@ module Loam
|
|
|
66
66
|
return false unless activated?
|
|
67
67
|
|
|
68
68
|
with_lock do
|
|
69
|
-
step =
|
|
69
|
+
step = OpenLoam::Totp.matching_step(totp_secret, code)
|
|
70
70
|
if step && (last_totp_step.nil? || step > last_totp_step)
|
|
71
71
|
update!(last_totp_step: step)
|
|
72
72
|
true
|
|
@@ -100,7 +100,7 @@ module Loam
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def provisioning_uri(issuer:)
|
|
103
|
-
|
|
103
|
+
OpenLoam::Totp.provisioning_uri(totp_secret, account: user.email, issuer: issuer)
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
private
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# One in-app message for one recipient (`user`) in one tenant. The same
|
|
3
3
|
# person in two tenants has two separate inboxes — like everything else in
|
|
4
|
-
#
|
|
4
|
+
# OpenLoam, "your notifications" always means "in the current tenant".
|
|
5
5
|
#
|
|
6
|
-
# Plumbing, not business data: not audited, the same way
|
|
7
|
-
# isn't. It IS evented, though — creating one publishes "
|
|
8
|
-
# (carrying the recipient's user_id) so
|
|
6
|
+
# Plumbing, not business data: not audited, the same way OpenLoam::AuditRecord
|
|
7
|
+
# isn't. It IS evented, though — creating one publishes "open_loam.notification.created"
|
|
8
|
+
# (carrying the recipient's user_id) so OpenLoam::EventStream can push it to that
|
|
9
9
|
# user's browser and the bell updates live. Created through
|
|
10
|
-
#
|
|
11
|
-
class Notification <
|
|
12
|
-
self.table_name = "
|
|
10
|
+
# OpenLoam::Notifications.notify, read through the admin bell.
|
|
11
|
+
class Notification < OpenLoam::TenantRecord
|
|
12
|
+
self.table_name = "open_loam_notifications"
|
|
13
13
|
|
|
14
14
|
belongs_to :user
|
|
15
15
|
|
|
@@ -18,9 +18,9 @@ module Loam
|
|
|
18
18
|
scope :unread, -> { where(read_at: nil) }
|
|
19
19
|
|
|
20
20
|
# The one signal the real-time bell listens for. Only id + recipient ride the
|
|
21
|
-
# event (
|
|
21
|
+
# event (OpenLoam::Events also stamps tenant_id); no message content is broadcast.
|
|
22
22
|
after_create_commit do
|
|
23
|
-
|
|
23
|
+
OpenLoam::Events.publish("open_loam.notification.created", id: id, user_id: user_id)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# What the notification is about, if anything (source_type/source_id) —
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A staged mutation awaiting human approval. When a caller runs under
|
|
3
|
-
# confirm-mode (an MCP tool acting for an AI agent — see
|
|
3
|
+
# confirm-mode (an MCP tool acting for an AI agent — see OpenLoam.mutation_mode), a
|
|
4
4
|
# write is staged HERE as a preview instead of committing; a manager approves
|
|
5
|
-
# or rejects, and only approval executes it. This is
|
|
5
|
+
# or rejects, and only approval executes it. This is OpenLoam's thesis made
|
|
6
6
|
# concrete: agent-triggered writes are gate-able.
|
|
7
7
|
#
|
|
8
|
-
# The approval gate IS a workflow (
|
|
8
|
+
# The approval gate IS a workflow (OpenLoam::Workflow): pending → (manager) approve
|
|
9
9
|
# / reject → executed / failed. "Who may approve" lives in one declarative,
|
|
10
|
-
# role-gated place. The proposed changes are encrypted at rest (
|
|
10
|
+
# role-gated place. The proposed changes are encrypted at rest (OpenLoam::Encryptable),
|
|
11
11
|
# because a staged change to an encrypted target field would otherwise sit here
|
|
12
12
|
# — and in this row's own audit — as plaintext, reopening the leak L-901 closed.
|
|
13
|
-
class PendingAction <
|
|
14
|
-
self.table_name = "
|
|
13
|
+
class PendingAction < OpenLoam::TenantRecord
|
|
14
|
+
self.table_name = "open_loam_pending_actions"
|
|
15
15
|
|
|
16
|
-
include
|
|
17
|
-
include
|
|
18
|
-
include
|
|
16
|
+
include OpenLoam::Auditable
|
|
17
|
+
include OpenLoam::Encryptable
|
|
18
|
+
include OpenLoam::Workflow
|
|
19
19
|
|
|
20
20
|
belongs_to :actor, class_name: "User", optional: true
|
|
21
21
|
belongs_to :reviewer, class_name: "User", foreign_key: :reviewed_by_id, optional: true
|
|
@@ -96,7 +96,7 @@ module Loam
|
|
|
96
96
|
# write; the record ends "executed" or "failed". Executes as `by`, so the
|
|
97
97
|
# TARGET's audit names the approving human — the person owns the change.
|
|
98
98
|
def approve!(by:)
|
|
99
|
-
|
|
99
|
+
OpenLoam.as_tenant(tenant, actor: by) do
|
|
100
100
|
reject_self_approval!(by)
|
|
101
101
|
self.reviewed_by_id = by.id
|
|
102
102
|
self.reviewed_at = Time.current
|
|
@@ -107,7 +107,7 @@ module Loam
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
def reject!(by:, reason: nil)
|
|
110
|
-
|
|
110
|
+
OpenLoam.as_tenant(tenant, actor: by) do
|
|
111
111
|
self.reviewed_by_id = by.id
|
|
112
112
|
self.reviewed_at = Time.current
|
|
113
113
|
self.result = [ "rejected", reason.presence ].compact.join(": ")
|
|
@@ -126,9 +126,9 @@ module Loam
|
|
|
126
126
|
# withdrawal, not a segregation-of-duties concern.)
|
|
127
127
|
def reject_self_approval!(by)
|
|
128
128
|
return unless by.id == actor_id
|
|
129
|
-
return if
|
|
129
|
+
return if OpenLoam::Configs.get("approvals.allow_self_approve", default: false)
|
|
130
130
|
|
|
131
|
-
raise
|
|
131
|
+
raise OpenLoam::NotAuthorizedError, "self-approval is not permitted; a different person must approve this change"
|
|
132
132
|
end
|
|
133
133
|
|
|
134
134
|
# Execution AND its status/result recording share ONE transaction with the
|
|
@@ -163,7 +163,7 @@ module Loam
|
|
|
163
163
|
target.respond_to?(:soft_delete!) ? target.soft_delete! : target.destroy!
|
|
164
164
|
"deleted #{target_type}##{target_id}"
|
|
165
165
|
else
|
|
166
|
-
raise
|
|
166
|
+
raise OpenLoam::Error, "unknown action_type #{action_type.inspect}"
|
|
167
167
|
end
|
|
168
168
|
end
|
|
169
169
|
|
|
@@ -178,31 +178,31 @@ module Loam
|
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
def encrypted_target_fields
|
|
181
|
-
return [] unless target_class.respond_to?(:
|
|
181
|
+
return [] unless target_class.respond_to?(:open_loam_encrypted_attributes)
|
|
182
182
|
|
|
183
|
-
target_class.
|
|
183
|
+
target_class.open_loam_encrypted_attributes.map(&:to_s)
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
# Set while a workflow transition is performing its save, so the validation
|
|
187
187
|
# below can tell a legitimate status move from a direct assignment.
|
|
188
|
-
def
|
|
189
|
-
@
|
|
188
|
+
def open_loam_perform_transition!(transition)
|
|
189
|
+
@open_loam_status_via_transition = true
|
|
190
190
|
super
|
|
191
191
|
ensure
|
|
192
|
-
@
|
|
192
|
+
@open_loam_status_via_transition = false
|
|
193
193
|
end
|
|
194
194
|
|
|
195
195
|
def status_changes_only_through_a_transition
|
|
196
196
|
return unless will_save_change_to_status?
|
|
197
|
-
return if @
|
|
197
|
+
return if @open_loam_status_via_transition
|
|
198
198
|
|
|
199
199
|
errors.add(:status, "may only change through an approval transition (approve!/reject!), not a direct write")
|
|
200
200
|
end
|
|
201
201
|
|
|
202
202
|
def status_starts_at_the_initial_state
|
|
203
|
-
return if status == self.class.
|
|
203
|
+
return if status == self.class.open_loam_workflow.initial
|
|
204
204
|
|
|
205
|
-
errors.add(:status, "must start at #{self.class.
|
|
205
|
+
errors.add(:status, "must start at #{self.class.open_loam_workflow.initial.inspect} — a staged action begins pending")
|
|
206
206
|
end
|
|
207
207
|
end
|
|
208
208
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A saved view of an entity's admin index — which columns show, active filters,
|
|
3
3
|
# sort, and page size — that a user can name, make their default, or share.
|
|
4
4
|
#
|
|
@@ -7,15 +7,15 @@ module Loam
|
|
|
7
7
|
# * "role" — everyone with the named membership role in the tenant.
|
|
8
8
|
# * "tenant" — everyone in the tenant.
|
|
9
9
|
# A private view is invisible to others by construction (see
|
|
10
|
-
#
|
|
10
|
+
# OpenLoam::Perspectives.visible_to), not merely by a permission check.
|
|
11
11
|
#
|
|
12
12
|
# Tenant-scoped like everything else, and audited. Optimistic-locked
|
|
13
13
|
# (lock_version) so two people editing a shared view don't silently clobber
|
|
14
14
|
# each other — the admin controller rescues ActiveRecord::StaleObjectError.
|
|
15
|
-
class Perspective <
|
|
16
|
-
self.table_name = "
|
|
15
|
+
class Perspective < OpenLoam::TenantRecord
|
|
16
|
+
self.table_name = "open_loam_perspectives"
|
|
17
17
|
|
|
18
|
-
include
|
|
18
|
+
include OpenLoam::Auditable
|
|
19
19
|
|
|
20
20
|
VISIBILITIES = %w[private role tenant].freeze
|
|
21
21
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Progress of a long-running job (a bulk import, a reindex, a report) so the
|
|
3
3
|
# admin can watch it live instead of guessing. Tenant-scoped; a job started in
|
|
4
4
|
# one tenant is only visible and streamable there.
|
|
@@ -7,11 +7,11 @@ module Loam
|
|
|
7
7
|
# ticks) and the audit trail would fill with noise. The terminal status and
|
|
8
8
|
# any error are captured on the row itself, which is the summary that matters.
|
|
9
9
|
#
|
|
10
|
-
# Each meaningful change publishes `
|
|
11
|
-
# carrying only id/percent/status — no record contents. See
|
|
10
|
+
# Each meaningful change publishes `open_loam.progress.updated` (broadcastable → SSE)
|
|
11
|
+
# carrying only id/percent/status — no record contents. See OpenLoam::Progress for
|
|
12
12
|
# the entry point.
|
|
13
|
-
class ProgressJob <
|
|
14
|
-
self.table_name = "
|
|
13
|
+
class ProgressJob < OpenLoam::TenantRecord
|
|
14
|
+
self.table_name = "open_loam_progress_jobs"
|
|
15
15
|
|
|
16
16
|
STATUSES = %w[running completed failed cancelled].freeze
|
|
17
17
|
STALE_AFTER = 5.minutes
|
|
@@ -93,11 +93,11 @@ module Loam
|
|
|
93
93
|
self
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
# Only ever id/percent/status leave the server (
|
|
96
|
+
# Only ever id/percent/status leave the server (OpenLoam::Events stamps tenant_id
|
|
97
97
|
# for the SSE deliverable filter; the frame's safe_payload drops everything
|
|
98
98
|
# else).
|
|
99
99
|
def broadcast
|
|
100
|
-
|
|
100
|
+
OpenLoam::Events.publish("open_loam.progress.updated", id: id, percent: percent, status: status)
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# An advisory "someone is editing this" lock on a record — the COURTESY layer.
|
|
3
3
|
# The real guarantee against a silent clobber is optimistic locking
|
|
4
|
-
# (lock_version, see
|
|
4
|
+
# (lock_version, see OpenLoam::RecordLocks docs); this only warns the second
|
|
5
5
|
# editor. One lock per record, with a TTL: an expired lock is treated as free,
|
|
6
6
|
# and re-acquiring as the same holder extends it (a heartbeat). Plumbing, so
|
|
7
|
-
# not audited — like
|
|
8
|
-
class RecordLock <
|
|
9
|
-
self.table_name = "
|
|
7
|
+
# not audited — like OpenLoam::ApiToken.
|
|
8
|
+
class RecordLock < OpenLoam::TenantRecord
|
|
9
|
+
self.table_name = "open_loam_record_locks"
|
|
10
10
|
|
|
11
11
|
belongs_to :locked_by, class_name: "User"
|
|
12
12
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A recurring job that runs on a schedule (cron or interval). Tenant-scoped:
|
|
3
3
|
# a "tenant"-scope schedule enqueues its job in this tenant on schedule; a
|
|
4
4
|
# "system"-scope schedule enqueues once with no tenant context. Audited on
|
|
5
5
|
# config changes (not per run — runs are tracked by last_run_at/next_run_at).
|
|
6
6
|
#
|
|
7
7
|
# SECURITY: job_class is validated to be a known ActiveJob (see
|
|
8
|
-
#
|
|
8
|
+
# OpenLoam::Scheduler.resolve_job_class) at save AND at enqueue — a scheduler that
|
|
9
9
|
# constantized and ran arbitrary user input would be a code-execution hole.
|
|
10
|
-
class ScheduledJob <
|
|
11
|
-
self.table_name = "
|
|
10
|
+
class ScheduledJob < OpenLoam::TenantRecord
|
|
11
|
+
self.table_name = "open_loam_scheduled_jobs"
|
|
12
12
|
|
|
13
|
-
include
|
|
13
|
+
include OpenLoam::Auditable
|
|
14
14
|
|
|
15
15
|
SCOPES = %w[tenant system].freeze
|
|
16
16
|
LOCK_TTL = 5.minutes # how long a claim holds a due job before another tick may retry it
|
|
@@ -26,11 +26,11 @@ module Loam
|
|
|
26
26
|
# The next fire time strictly after `from`, in this job's timezone (UTC when
|
|
27
27
|
# unset). Stored in next_run_at as UTC.
|
|
28
28
|
def compute_next_run(from = Time.current)
|
|
29
|
-
|
|
29
|
+
OpenLoam::Cron.next_after(schedule, from: from, zone: timezone.presence || "UTC")
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def job_class_constant
|
|
33
|
-
|
|
33
|
+
OpenLoam::Scheduler.resolve_job_class(job_class)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
private
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# The word-level search index maintained by OpenLoam::Search::TokenDriver: one row
|
|
3
|
+
# per (record, token), tenant-scoped like every OpenLoam entity. Plumbing — not
|
|
4
|
+
# audited, not soft-deletable. Only the TokenDriver reads or writes it; under
|
|
5
|
+
# the default LikeDriver this table simply stays empty.
|
|
6
|
+
class SearchToken < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_search_tokens"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Links an external IdP identity (a provider's stable `sub`) to a local User.
|
|
3
3
|
# The durable key SSO callbacks resolve on — an email can change at the IdP,
|
|
4
4
|
# `sub` does not. Tenant-scoped (created and read inside the provider's tenant).
|
|
5
|
-
class SsoIdentity <
|
|
6
|
-
self.table_name = "
|
|
5
|
+
class SsoIdentity < OpenLoam::TenantRecord
|
|
6
|
+
self.table_name = "open_loam_sso_identities"
|
|
7
7
|
|
|
8
8
|
belongs_to :user
|
|
9
|
-
belongs_to :sso_provider, class_name: "
|
|
9
|
+
belongs_to :sso_provider, class_name: "OpenLoam::SsoProvider"
|
|
10
10
|
|
|
11
11
|
validates :sub, presence: true, uniqueness: { scope: :sso_provider_id }
|
|
12
12
|
end
|