open-loam 0.1.1 → 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 +32 -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,17 +1,17 @@
|
|
|
1
|
-
module
|
|
2
|
-
# An MCP (Model Context Protocol) server surface exposing
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# An MCP (Model Context Protocol) server surface exposing OpenLoam to an AI agent —
|
|
3
3
|
# tools-only v1 (L-302). It lets an agent DISCOVER the domain (entities, schema,
|
|
4
4
|
# policy, workflow) and READ tenant-scoped records, and PROPOSE writes that are
|
|
5
5
|
# STAGED for human approval — never committed. Everything runs inside the
|
|
6
6
|
# tenant + actor of the API token the server authenticated with; whatever that
|
|
7
7
|
# user may do, no more. Approval authority stays with a human in the admin.
|
|
8
8
|
#
|
|
9
|
-
# Two layers: the tool methods (pure, assume
|
|
9
|
+
# Two layers: the tool methods (pure, assume OpenLoam::Current is established) and
|
|
10
10
|
# `handle_jsonrpc` (a pure JSON-RPC dispatcher for initialize / tools/list /
|
|
11
11
|
# tools/call). The stdio transport (newline-delimited JSON-RPC) lives in
|
|
12
|
-
#
|
|
12
|
+
# OpenLoam::Mcp::Server.
|
|
13
13
|
#
|
|
14
|
-
# SECURITY posture, reusing
|
|
14
|
+
# SECURITY posture, reusing OpenLoam's existing gates:
|
|
15
15
|
# * entity names resolve against an allowlist (API-exposed TenantRecord
|
|
16
16
|
# descendants), never a bare constantize;
|
|
17
17
|
# * query filters/order are whitelisted to real columns (or a known custom
|
|
@@ -32,7 +32,7 @@ module Loam
|
|
|
32
32
|
TOOLS = [
|
|
33
33
|
{
|
|
34
34
|
name: "list_entities",
|
|
35
|
-
description: "List the business entities available in this
|
|
35
|
+
description: "List the business entities available in this OpenLoam tenant.",
|
|
36
36
|
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
37
37
|
},
|
|
38
38
|
{
|
|
@@ -72,7 +72,7 @@ module Loam
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
name: "stage_write",
|
|
75
|
-
description: "PROPOSE an update to one record. It is staged as a
|
|
75
|
+
description: "PROPOSE an update to one record. It is staged as a OpenLoam PendingAction for a human manager to approve — it does NOT take effect until approved. Only policy-writable columns are accepted; the workflow column is refused (use a transition).",
|
|
76
76
|
inputSchema: {
|
|
77
77
|
type: "object",
|
|
78
78
|
properties: {
|
|
@@ -91,9 +91,9 @@ module Loam
|
|
|
91
91
|
|
|
92
92
|
def entities
|
|
93
93
|
# Models are lazy-loaded (Zeitwerk), so `descendants` is only complete after
|
|
94
|
-
# an eager load — same as
|
|
94
|
+
# an eager load — same as OpenLoam::OpenApi's discovery.
|
|
95
95
|
Rails.application.eager_load! if defined?(Rails) && Rails.respond_to?(:application)
|
|
96
|
-
|
|
96
|
+
OpenLoam::TenantRecord.descendants
|
|
97
97
|
.reject { |model| model.name.blank? }
|
|
98
98
|
.select { |model| api_exposed?(model) }
|
|
99
99
|
.sort_by(&:name)
|
|
@@ -117,7 +117,7 @@ module Loam
|
|
|
117
117
|
|
|
118
118
|
def describe_entity(entity:)
|
|
119
119
|
model = resolve!(entity)
|
|
120
|
-
policy =
|
|
120
|
+
policy = OpenLoam::Policy.for(model.new)
|
|
121
121
|
|
|
122
122
|
columns = model.columns.reject { |c| c.name.end_with?("_hash") }.map do |col|
|
|
123
123
|
{ name: col.name, type: col.sql_type_metadata.type.to_s,
|
|
@@ -134,8 +134,8 @@ module Loam
|
|
|
134
134
|
[]
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
workflow = if model.respond_to?(:
|
|
138
|
-
wf = model.
|
|
137
|
+
workflow = if model.respond_to?(:open_loam_workflow) && model.open_loam_workflow
|
|
138
|
+
wf = model.open_loam_workflow
|
|
139
139
|
{ column: wf.column, states: wf.states,
|
|
140
140
|
transitions: wf.transitions.values.map { |t| { name: t.name.to_s, from: t.from, to: t.to, roles: Array(t.roles).map(&:to_s) } } }
|
|
141
141
|
end
|
|
@@ -145,7 +145,7 @@ module Loam
|
|
|
145
145
|
|
|
146
146
|
def query_entity(entity:, filters: [], order: nil, dir: "asc", limit: MAX_LIMIT)
|
|
147
147
|
model = resolve!(entity)
|
|
148
|
-
policy =
|
|
148
|
+
policy = OpenLoam::Policy.for(model.new)
|
|
149
149
|
scope = apply_filters(model, model.all, filters, policy)
|
|
150
150
|
scope = apply_order(model, scope, order, dir)
|
|
151
151
|
capped = [ [ limit.to_i, 1 ].max, MAX_LIMIT ].min
|
|
@@ -157,8 +157,8 @@ module Loam
|
|
|
157
157
|
def stage_write(entity:, id:, changes:)
|
|
158
158
|
model = resolve!(entity)
|
|
159
159
|
record = model.find(id) # tenant-scoped
|
|
160
|
-
policy =
|
|
161
|
-
workflow_column = model.respond_to?(:
|
|
160
|
+
policy = OpenLoam::Policy.for(record)
|
|
161
|
+
workflow_column = model.respond_to?(:open_loam_workflow) ? model.open_loam_workflow&.column.to_s : nil
|
|
162
162
|
|
|
163
163
|
clean = {}
|
|
164
164
|
(changes || {}).each do |field, value|
|
|
@@ -177,7 +177,7 @@ module Loam
|
|
|
177
177
|
end
|
|
178
178
|
raise ToolError, "no writable changes" if clean.empty?
|
|
179
179
|
|
|
180
|
-
action =
|
|
180
|
+
action = OpenLoam::PendingActions.stage(
|
|
181
181
|
summary: "MCP proposal: update #{model.name}##{id}",
|
|
182
182
|
on: record, action: :update, changes: clean
|
|
183
183
|
)
|
|
@@ -206,14 +206,14 @@ module Loam
|
|
|
206
206
|
when "initialize"
|
|
207
207
|
ok(id, protocolVersion: request.dig("params", "protocolVersion") || PROTOCOL_VERSION,
|
|
208
208
|
capabilities: { tools: {} },
|
|
209
|
-
serverInfo: { name: "
|
|
209
|
+
serverInfo: { name: "open_loam", version: OpenLoam::VERSION })
|
|
210
210
|
when "tools/list"
|
|
211
211
|
ok(id, tools: TOOLS)
|
|
212
212
|
when "tools/call"
|
|
213
213
|
begin
|
|
214
214
|
data = call_tool(request.dig("params", "name"), request.dig("params", "arguments"))
|
|
215
215
|
ok(id, content: [ { type: "text", text: JSON.generate(data) } ])
|
|
216
|
-
rescue ToolError,
|
|
216
|
+
rescue ToolError, OpenLoam::Error, ActiveRecord::RecordNotFound => error
|
|
217
217
|
ok(id, isError: true, content: [ { type: "text", text: error.message } ])
|
|
218
218
|
end
|
|
219
219
|
when "notifications/initialized", "notifications/cancelled", nil
|
|
@@ -239,8 +239,8 @@ module Loam
|
|
|
239
239
|
# CustomFieldIndex.filter carries its own L-711 read-ACL for the current
|
|
240
240
|
# actor; surface its refusal as a clean tool error.
|
|
241
241
|
begin
|
|
242
|
-
scope = scope.merge(
|
|
243
|
-
rescue
|
|
242
|
+
scope = scope.merge(OpenLoam::CustomFieldIndex.filter(model, field, op, value))
|
|
243
|
+
rescue OpenLoam::FieldAccessError => error
|
|
244
244
|
raise ToolError, error.message
|
|
245
245
|
end
|
|
246
246
|
else
|
|
@@ -274,7 +274,7 @@ module Loam
|
|
|
274
274
|
|
|
275
275
|
def serialize(record, policy)
|
|
276
276
|
model = record.class
|
|
277
|
-
encrypted = model.respond_to?(:
|
|
277
|
+
encrypted = model.respond_to?(:open_loam_encrypted_attributes) ? model.open_loam_encrypted_attributes.map(&:to_s) : []
|
|
278
278
|
|
|
279
279
|
json = {}
|
|
280
280
|
policy.readable_fields(model.column_names).each do |col|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# The one way to tell someone something inside the app.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# OpenLoam::Notifications.notify(user, title: "Order approved", source: order)
|
|
5
|
+
# OpenLoam::Notifications.notify_role(:manager, title: "New damage report")
|
|
6
6
|
#
|
|
7
|
-
# Records land in the CURRENT tenant (
|
|
7
|
+
# Records land in the CURRENT tenant (OpenLoam::Notification is tenant-scoped),
|
|
8
8
|
# so a notification can never be delivered across a tenant boundary.
|
|
9
9
|
#
|
|
10
10
|
# The intended pattern is event -> notification: subscribe to a domain event
|
|
11
|
-
# in config/initializers/
|
|
11
|
+
# in config/initializers/open_loam.rb and notify from there, instead of scattering
|
|
12
12
|
# delivery calls through models and controllers.
|
|
13
13
|
module Notifications
|
|
14
14
|
# Returns the created notifications. Accepts one user or many; `source` is
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module OpenLoam
|
|
4
4
|
# Auto-generates an OpenAPI 3.1 document for the app's JSON API by
|
|
5
|
-
# INTROSPECTING what
|
|
5
|
+
# INTROSPECTING what OpenLoam already knows — the generated `Api::<Plural>Controller`s,
|
|
6
6
|
# each entity's columns/types, its exposed `FIELDS`, its custom fields, and that
|
|
7
7
|
# every endpoint is bearer-authenticated and tenant-scoped. No hand-written
|
|
8
8
|
# annotations, no external gem.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# OpenLoam::OpenApi.document # => a Hash conforming to OpenAPI 3.1
|
|
11
|
+
# OpenLoam::OpenApi.markdown # => a Markdown rendering
|
|
12
12
|
#
|
|
13
13
|
# The document describes the SHAPE of the API only — never tenant data. It is
|
|
14
|
-
# served at /admin/api_docs and exported by `bin/rails
|
|
14
|
+
# served at /admin/api_docs and exported by `bin/rails open_loam:openapi:export`.
|
|
15
15
|
module OpenApi
|
|
16
16
|
TYPE_MAP = {
|
|
17
17
|
"string" => { "type" => "string" }, "text" => { "type" => "string" },
|
|
@@ -40,13 +40,13 @@ module Loam
|
|
|
40
40
|
}
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# The
|
|
44
|
-
# subclasses (Class.new(
|
|
43
|
+
# The OpenLoam entities that have a generated JSON API controller. Anonymous
|
|
44
|
+
# subclasses (Class.new(OpenLoam::TenantRecord), common in tests) have no name,
|
|
45
45
|
# so model_name would raise "Class name cannot be blank" — skip them: a
|
|
46
46
|
# nameless class has no controller or route to document anyway.
|
|
47
47
|
def api_entities
|
|
48
48
|
Rails.application.eager_load! if defined?(Rails) && Rails.respond_to?(:application)
|
|
49
|
-
|
|
49
|
+
OpenLoam::TenantRecord.descendants
|
|
50
50
|
.reject { |model| model.name.blank? }
|
|
51
51
|
.select { |model| controller_for(model) }
|
|
52
52
|
.sort_by(&:name)
|
|
@@ -59,17 +59,17 @@ module Loam
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def info
|
|
62
|
-
name = defined?(Rails) ? Rails.application.class.module_parent_name : "
|
|
62
|
+
name = defined?(Rails) ? Rails.application.class.module_parent_name : "OpenLoam"
|
|
63
63
|
{
|
|
64
64
|
"title" => "#{name} API",
|
|
65
65
|
"version" => "1.0.0",
|
|
66
|
-
"description" => "Auto-generated from the
|
|
66
|
+
"description" => "Auto-generated from the OpenLoam entities. Bearer-token authenticated. #{TENANCY_NOTE}"
|
|
67
67
|
}
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def security_schemes
|
|
71
71
|
{ "bearerAuth" => { "type" => "http", "scheme" => "bearer",
|
|
72
|
-
"description" => "A
|
|
72
|
+
"description" => "A OpenLoam::ApiToken — identifies one user in one tenant." } }
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def schemas
|
|
@@ -89,7 +89,7 @@ module Loam
|
|
|
89
89
|
|
|
90
90
|
# Request body: the writable, declared fields only — never id/tenant_id/
|
|
91
91
|
# timestamps. Field-level write access is enforced per the token's role at
|
|
92
|
-
# runtime (
|
|
92
|
+
# runtime (OpenLoam::Policy), which a structural schema can't express per-role;
|
|
93
93
|
# noted in the description rather than emitting a schema per role.
|
|
94
94
|
def input_schema(model)
|
|
95
95
|
props = {}
|
|
@@ -143,9 +143,9 @@ module Loam
|
|
|
143
143
|
return controller::FIELDS.map(&:to_s) if controller.const_defined?(:FIELDS)
|
|
144
144
|
|
|
145
145
|
# No declared FIELDS: fall back to columns, but NEVER surface an encrypted
|
|
146
|
-
# column or its blind-index `_hash` (same exclusion as
|
|
147
|
-
encrypted = model.respond_to?(:
|
|
148
|
-
blind = model.respond_to?(:
|
|
146
|
+
# column or its blind-index `_hash` (same exclusion as OpenLoam::Export).
|
|
147
|
+
encrypted = model.respond_to?(:open_loam_encrypted_attributes) ? model.open_loam_encrypted_attributes : []
|
|
148
|
+
blind = model.respond_to?(:open_loam_searchable_encrypted_attributes) ? model.open_loam_searchable_encrypted_attributes.map { |a| "#{a}_hash" } : []
|
|
149
149
|
model.column_names - plumbing(model) - encrypted - blind
|
|
150
150
|
end
|
|
151
151
|
|
|
@@ -161,7 +161,7 @@ module Loam
|
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
def custom_field_names(model)
|
|
164
|
-
return [] unless model.respond_to?(:custom_field_definitions) &&
|
|
164
|
+
return [] unless model.respond_to?(:custom_field_definitions) && OpenLoam::Current.tenant
|
|
165
165
|
|
|
166
166
|
model.custom_field_definitions.map(&:name)
|
|
167
167
|
rescue StandardError
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
module
|
|
2
|
-
# Disable or replace an entry in one of
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# Disable or replace an entry in one of OpenLoam's OWN keyed registries from an
|
|
3
3
|
# initializer — customization without forking the gem, and without
|
|
4
4
|
# monkeypatching. This is deliberately SMALL: Rails already handles structural
|
|
5
5
|
# overriding (shadow a view/controller by path, prepend a module). Overrides
|
|
6
6
|
# only covers the in-gem keyed registries, where path-shadowing doesn't reach:
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# OpenLoam::Overrides.disable(:widgets, "open_progress") # drop a built-in widget
|
|
9
|
+
# OpenLoam::Overrides.replace(:widgets, "audit_recent") { |actor| { kind: "count", value: 0 } }
|
|
10
|
+
# OpenLoam::Overrides.disable(:broadcast_events, "open_loam.progress.") # stop pushing an event over SSE
|
|
11
11
|
#
|
|
12
12
|
# THE VALUE-ADD over raw monkeypatching: a stale override (a typo, or an entry
|
|
13
13
|
# that no longer exists) is caught at boot by `check!` and warned about, so a
|
|
@@ -19,7 +19,7 @@ module Loam
|
|
|
19
19
|
# doesn't wrap those. Controllers/views/routes are NOT here — that's Rails'
|
|
20
20
|
# path-shadowing job (see docs).
|
|
21
21
|
module Overrides
|
|
22
|
-
DISABLED = :
|
|
22
|
+
DISABLED = :__open_loam_disabled__
|
|
23
23
|
|
|
24
24
|
class << self
|
|
25
25
|
def disable(registry, key)
|
|
@@ -81,7 +81,7 @@ module Loam
|
|
|
81
81
|
def check!
|
|
82
82
|
found = stale
|
|
83
83
|
found.each do |registry, key|
|
|
84
|
-
message = "[
|
|
84
|
+
message = "[open_loam] stale override: #{registry} has no entry #{key.inspect} to disable/replace — the override is doing nothing."
|
|
85
85
|
logger ? logger.warn(message) : warn(message)
|
|
86
86
|
end
|
|
87
87
|
found
|
|
@@ -96,9 +96,9 @@ module Loam
|
|
|
96
96
|
# The live keys of an introspectable registry, or nil when we can't tell.
|
|
97
97
|
def known_keys(registry)
|
|
98
98
|
case registry.to_sym
|
|
99
|
-
when :widgets then
|
|
100
|
-
when :broadcast_events then
|
|
101
|
-
when :scheduler then defined?(
|
|
99
|
+
when :widgets then OpenLoam::Widgets.keys
|
|
100
|
+
when :broadcast_events then OpenLoam.broadcast_events
|
|
101
|
+
when :scheduler then defined?(OpenLoam::Scheduler) ? OpenLoam::Scheduler.registered.map { |d| d[:key] } : nil
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
module
|
|
2
|
-
# Staging and reviewing gated mutations (see
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# Staging and reviewing gated mutations (see OpenLoam::PendingAction).
|
|
3
3
|
#
|
|
4
|
-
# pending =
|
|
4
|
+
# pending = OpenLoam::PendingActions.stage(
|
|
5
5
|
# summary: "Raise the excavator's daily rate to 1100",
|
|
6
6
|
# on: equipment, action: :update, changes: { daily_rate: 1100 }
|
|
7
7
|
# )
|
|
8
8
|
# pending.preview # => { "daily_rate" => { "from" => 950, "to" => 1100 } }
|
|
9
9
|
# pending.approve!(by: manager) # role-gated; executes the change
|
|
10
10
|
#
|
|
11
|
-
# `stage` NEVER touches the target — it only records the intent.
|
|
11
|
+
# `stage` NEVER touches the target — it only records the intent. OpenLoam does not
|
|
12
12
|
# intercept Active Record globally (that would be fragile and out of scope);
|
|
13
13
|
# this is the primitive a confirm-mode write path calls instead of saving.
|
|
14
14
|
module PendingActions
|
|
15
15
|
class << self
|
|
16
|
-
def stage(summary:, on:, action:, changes: {}, idempotency_key: nil, actor:
|
|
16
|
+
def stage(summary:, on:, action:, changes: {}, idempotency_key: nil, actor: OpenLoam::Current.actor)
|
|
17
17
|
target_type, target_id = resolve_target(on)
|
|
18
18
|
changes = changes.transform_keys(&:to_s)
|
|
19
19
|
key = idempotency_key || compute_key(target_type, target_id, action, changes)
|
|
@@ -21,10 +21,10 @@ module Loam
|
|
|
21
21
|
# The same proposal staged twice collapses to one row — but only a still
|
|
22
22
|
# PENDING one. A rejected or executed proposal with this key may be
|
|
23
23
|
# re-staged as a fresh pending row (the partial index allows the coexist).
|
|
24
|
-
existing =
|
|
24
|
+
existing = OpenLoam::PendingAction.pending.find_by(idempotency_key: key)
|
|
25
25
|
return existing if existing
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
OpenLoam::PendingAction.create!(
|
|
28
28
|
actor_id: actor&.id,
|
|
29
29
|
action_type: action.to_s,
|
|
30
30
|
target_type: target_type,
|
|
@@ -35,7 +35,7 @@ module Loam
|
|
|
35
35
|
)
|
|
36
36
|
rescue ActiveRecord::RecordNotUnique
|
|
37
37
|
# Lost a concurrent double-stage race — return the pending row that won.
|
|
38
|
-
|
|
38
|
+
OpenLoam::PendingAction.pending.find_by!(idempotency_key: key)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
private
|
|
@@ -53,7 +53,7 @@ module Loam
|
|
|
53
53
|
# the same key; a reordered hash is treated as a different proposal.
|
|
54
54
|
def compute_key(target_type, target_id, action, changes)
|
|
55
55
|
payload = [ target_type, target_id, action, changes.to_json ].join("|")
|
|
56
|
-
|
|
56
|
+
OpenLoam::Encryption.blind_index_scoped(payload, "tenant/#{OpenLoam.tenant!.id}")
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Feature-string permissions with wildcards (L-705) — a fine-grained capability
|
|
3
3
|
# layer that sits UNDER the coarse role. Roles answer "manager or clerk";
|
|
4
4
|
# permissions answer "may this role do `equipment.edit`" without inventing a new
|
|
5
|
-
# role for every distinction. Orthogonal to
|
|
6
|
-
# writes on a record) and to
|
|
5
|
+
# role for every distinction. Orthogonal to OpenLoam::Policy (which gates field-level
|
|
6
|
+
# writes on a record) and to OpenLoam::Features (a per-tenant capability switch).
|
|
7
7
|
#
|
|
8
8
|
# Declared once, in the initializer (like broadcast_events / scheduler defaults):
|
|
9
9
|
#
|
|
10
|
-
#
|
|
10
|
+
# OpenLoam::Permissions.configure do
|
|
11
11
|
# role :admin, allow: "*" # everything
|
|
12
12
|
# role :manager, allow: %w[equipment.* damage_report.* billing.read]
|
|
13
13
|
# role :clerk, allow: %w[equipment.read damage_report.create]
|
|
14
14
|
# end
|
|
15
15
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
16
|
+
# OpenLoam::Permissions.allow?(:clerk, "equipment.read") # => true
|
|
17
|
+
# OpenLoam::Permissions.allow?(:clerk, "equipment.edit") # => false
|
|
18
|
+
# OpenLoam.can?("equipment.edit") # for the current actor's role
|
|
19
19
|
#
|
|
20
20
|
# DENY BY DEFAULT: a role with no matching grant (or no grants at all) is denied.
|
|
21
21
|
#
|
|
@@ -72,7 +72,7 @@ module Loam
|
|
|
72
72
|
# Tiny DSL so `configure { role ... }` reads well without a receiver.
|
|
73
73
|
class DSL
|
|
74
74
|
def role(name, allow:)
|
|
75
|
-
|
|
75
|
+
OpenLoam::Permissions.role(name, allow: allow)
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
end
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
module
|
|
2
|
-
# Reading the saved views (
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# Reading the saved views (OpenLoam::Perspective) a user may see for an entity.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# OpenLoam::Perspectives.visible_to("Equipment", user: current_actor) # pick list
|
|
5
|
+
# OpenLoam::Perspectives.default_for("Equipment", user: current_actor) # the applicable default
|
|
6
|
+
# OpenLoam::Perspectives.resolve("Equipment", user:, id: params[:perspective_id])
|
|
7
7
|
#
|
|
8
|
-
# Every query is tenant-scoped by
|
|
8
|
+
# Every query is tenant-scoped by OpenLoam::Perspective. The membership role used
|
|
9
9
|
# for role-shared views is the user's role in the CURRENT tenant.
|
|
10
10
|
module Perspectives
|
|
11
11
|
class << self
|
|
12
12
|
# The views this user may see for an entity: their own private ones, the
|
|
13
13
|
# role-shared ones matching their membership role, and the tenant-wide
|
|
14
14
|
# ones. Default(s) first, then by name. Named `visible_to` rather than
|
|
15
|
-
# `for` so it does not read like
|
|
15
|
+
# `for` so it does not read like OpenLoam::Policy.for (which builds a policy).
|
|
16
16
|
def visible_to(entity_type, user:)
|
|
17
|
-
base =
|
|
17
|
+
base = OpenLoam::Perspective.where(entity_type: entity_type.to_s)
|
|
18
18
|
role = membership_role(user)
|
|
19
19
|
|
|
20
20
|
# Build the OR chain on UNORDERED relations — `.or` refuses to combine
|
|
@@ -55,7 +55,7 @@ module Loam
|
|
|
55
55
|
def membership_role(user)
|
|
56
56
|
return nil unless user
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
OpenLoam::Membership.find_by(user_id: user.id)&.role
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Base policy. One policy class per entity, one instance per (actor, record)
|
|
3
3
|
# pair. Action checks (read?/create?/update?/destroy?) default to "any member
|
|
4
4
|
# of the current tenant"; field-level write access is declared, not coded:
|
|
5
5
|
#
|
|
6
|
-
# class EquipmentPolicy <
|
|
6
|
+
# class EquipmentPolicy < OpenLoam::Policy
|
|
7
7
|
# field :daily_rate, writable: [:manager]
|
|
8
8
|
# end
|
|
9
9
|
#
|
|
10
|
-
# Roles come from
|
|
10
|
+
# Roles come from OpenLoam::Membership (actor + current tenant -> role).
|
|
11
11
|
class Policy
|
|
12
12
|
class << self
|
|
13
13
|
def field_rules
|
|
@@ -22,7 +22,7 @@ module Loam
|
|
|
22
22
|
policy_class = "#{record.class.name}Policy".safe_constantize
|
|
23
23
|
raise Error, "No policy defined for #{record.class.name} (expected #{record.class.name}Policy)" unless policy_class
|
|
24
24
|
|
|
25
|
-
policy_class.new(
|
|
25
|
+
policy_class.new(OpenLoam::Current.actor, record)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -36,7 +36,7 @@ module Loam
|
|
|
36
36
|
def role
|
|
37
37
|
return nil unless actor
|
|
38
38
|
|
|
39
|
-
@role ||=
|
|
39
|
+
@role ||= OpenLoam::Membership.find_by(user_id: actor.id)&.role&.to_sym
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def member? = role.present?
|
|
@@ -73,7 +73,7 @@ module Loam
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
# Same semantics as the static `field writable:` declaration, but for a
|
|
76
|
-
# runtime
|
|
76
|
+
# runtime OpenLoam::FieldDefinition (see OpenLoam::CustomFields): no
|
|
77
77
|
# writable_roles recorded means any member may write it.
|
|
78
78
|
def custom_field_writable?(field_name)
|
|
79
79
|
return false unless member?
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Entry point for progress tracking. Start a job, then drive it through the
|
|
3
|
-
# returned
|
|
3
|
+
# returned OpenLoam::ProgressJob:
|
|
4
4
|
#
|
|
5
|
-
# progress =
|
|
5
|
+
# progress = OpenLoam::Progress.start(name: "Reindex", total: equipment.count)
|
|
6
6
|
# equipment.find_each do |record|
|
|
7
7
|
# break if progress.cancelled? # cooperative cancel
|
|
8
8
|
# do_work(record)
|
|
@@ -12,18 +12,18 @@ module Loam
|
|
|
12
12
|
#
|
|
13
13
|
# The job is created in the current tenant (and stamped with the current
|
|
14
14
|
# actor), so it is only visible and streamable there. In a background job,
|
|
15
|
-
# establish the tenant with
|
|
15
|
+
# establish the tenant with OpenLoam.as_tenant first, like the webhook/digest jobs.
|
|
16
16
|
module Progress
|
|
17
17
|
module_function
|
|
18
18
|
|
|
19
19
|
def start(name:, total:, key: nil)
|
|
20
|
-
|
|
20
|
+
OpenLoam::ProgressJob.create!(
|
|
21
21
|
key: key,
|
|
22
22
|
name: name,
|
|
23
23
|
total: total.to_i,
|
|
24
24
|
completed: 0,
|
|
25
25
|
status: "running",
|
|
26
|
-
actor_id:
|
|
26
|
+
actor_id: OpenLoam.actor&.id,
|
|
27
27
|
started_at: Time.current
|
|
28
28
|
)
|
|
29
29
|
end
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
module
|
|
2
|
-
# Advisory record locks (
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# Advisory record locks (OpenLoam::RecordLock) — a "who's editing this" courtesy
|
|
3
3
|
# for the multi-user admin.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
5
|
+
# OpenLoam::RecordLocks.acquire(record, by: current_actor) # take/refresh, or nil if held
|
|
6
|
+
# OpenLoam::RecordLocks.holder(record) # the user holding it, or nil
|
|
7
|
+
# OpenLoam::RecordLocks.release(record, by: current_actor) # give it up
|
|
8
|
+
# OpenLoam::RecordLocks.force_release(record) # manager override
|
|
9
9
|
#
|
|
10
10
|
# THIS IS ADVISORY: a held lock warns, it does not hard-block — optimistic
|
|
11
11
|
# locking (lock_version) is the actual guarantee that two edits can't clobber.
|
|
12
|
-
# Every query is tenant-scoped by
|
|
12
|
+
# Every query is tenant-scoped by OpenLoam::RecordLock, so a lock is invisible and
|
|
13
13
|
# unaffectable from another tenant.
|
|
14
14
|
module RecordLocks
|
|
15
15
|
DEFAULT_TTL = 5.minutes
|
|
@@ -25,7 +25,7 @@ module Loam
|
|
|
25
25
|
lock = lock_row(record)
|
|
26
26
|
return nil if lock && !lock.expired? && lock.locked_by_id != by.id
|
|
27
27
|
|
|
28
|
-
lock ||=
|
|
28
|
+
lock ||= OpenLoam::RecordLock.new(lockable_type: type_of(record), lockable_id: record.id)
|
|
29
29
|
# A fresh session token when the lock is newly created or taken over from
|
|
30
30
|
# an expired holder; kept as-is on a heartbeat by the same user.
|
|
31
31
|
lock.token = SecureRandom.hex(16) if lock.new_record? || lock.locked_by_id != by.id
|
|
@@ -71,7 +71,7 @@ module Loam
|
|
|
71
71
|
private
|
|
72
72
|
|
|
73
73
|
def lock_row(record)
|
|
74
|
-
|
|
74
|
+
OpenLoam::RecordLock.find_by(lockable_type: type_of(record), lockable_id: record.id)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def type_of(record)
|