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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7fda058767d913097dee8faac85c9957d680233405c3d57004cf786a9ccc155d
|
|
4
|
+
data.tar.gz: 87763cfa7c0fa23c24111d8dceda31d73c491b6b8ead4cd1559aaeb742c7df2e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d40e81813088308a110726cea561b9a2fa4496658e95b7292e64af28473d2363a6b629546892989fae52abd05cd13bddc76578fdd08257fed60f8736ca37efc
|
|
7
|
+
data.tar.gz: 97caae5c7991ccadff19905a0224959aa9c40b1c446dfc39dcae4a51bb575ce685c579d24836e92069c7b8b4788c20002d519e3e613556967c4e43e63bd90f87
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
Notable changes to
|
|
3
|
+
Notable changes to OpenLoam. This project follows [semantic versioning](https://semver.org),
|
|
4
4
|
with the caveat that 0.x releases may break the public surface; the
|
|
5
5
|
[backward-compatibility contract](BACKWARD_COMPATIBILITY.md) names what is
|
|
6
6
|
frozen and what is not.
|
|
7
7
|
|
|
8
|
+
## 0.2.0 — 2026-09-05
|
|
9
|
+
|
|
10
|
+
### BREAKING
|
|
11
|
+
|
|
12
|
+
- **Ruby namespace renamed `Loam` → `OpenLoam`**, and every dependent
|
|
13
|
+
surface with it: the CLI (`rails g open_loam:install`,
|
|
14
|
+
`rails g open_loam:entity`, `bin/rails open_loam:mcp:serve`, etc.), all
|
|
15
|
+
`loam_*`-prefixed database tables and columns, `LOAM_*` env vars (now
|
|
16
|
+
`OPEN_LOAM_*`), and the outbound webhook signature header
|
|
17
|
+
(`X-Loam-Signature` → `X-OpenLoam-Signature`, tracked in
|
|
18
|
+
[BACKWARD_COMPATIBILITY.md](BACKWARD_COMPATIBILITY.md)). The RubyGems
|
|
19
|
+
package name is unaffected — it stays `open-loam`, as it already was.
|
|
20
|
+
- No back-compat shim is provided for any of this. Per 0.1.0/0.1.1 above, no
|
|
21
|
+
production deployment exists yet, so there is nothing running against the
|
|
22
|
+
old names to break in place — an app installed from an 0.1.x generator
|
|
23
|
+
would need its own table/env-var renames to upgrade, which is why this
|
|
24
|
+
ships as a breaking 0.x release rather than a deprecation cycle.
|
|
25
|
+
- One literal is deliberately **not** renamed, ever: the encryption
|
|
26
|
+
key-derivation inputs in `lib/open_loam/encryption/key_provider.rb`
|
|
27
|
+
(the HKDF `SALT` and `info` strings) and the AAD prefix in
|
|
28
|
+
`lib/open_loam/encryption.rb#aad`. These are inputs to key/tag
|
|
29
|
+
derivation, not identifiers — changing any of them would silently
|
|
30
|
+
break decryption of already-encrypted data.
|
|
31
|
+
|
|
8
32
|
## 0.1.1 — 2026-09-04
|
|
9
33
|
|
|
10
34
|
No functional change. The packaged code is byte-identical to 0.1.0 — only CI
|
|
@@ -23,14 +47,14 @@ matters.
|
|
|
23
47
|
|
|
24
48
|
## 0.1.0 — 2026-09-04
|
|
25
49
|
|
|
26
|
-
First public release.
|
|
50
|
+
First public release. OpenLoam is a working, tested prototype: the foundation and
|
|
27
51
|
the agent workflow are complete end to end, but no production deployment has
|
|
28
52
|
used it yet. Evaluate it as a prototype, not as proven infrastructure.
|
|
29
53
|
|
|
30
54
|
### The foundation
|
|
31
55
|
|
|
32
|
-
- **Multi-tenancy** — `
|
|
33
|
-
the current tenant. A missing tenant context raises `
|
|
56
|
+
- **Multi-tenancy** — `OpenLoam::TenantRecord` scopes every query, job and event to
|
|
57
|
+
the current tenant. A missing tenant context raises `OpenLoam::MissingTenantError`
|
|
34
58
|
rather than silently widening a query.
|
|
35
59
|
- **Authorization** — roles, policies, field-level write access, and
|
|
36
60
|
deny-by-default wildcard feature permissions (`equipment.*`).
|
|
@@ -56,13 +80,13 @@ used it yet. Evaluate it as a prototype, not as proven infrastructure.
|
|
|
56
80
|
|
|
57
81
|
- **`AGENTS.md`** — the contract, generated into the host app, telling an agent
|
|
58
82
|
where code belongs and which boundaries it must preserve.
|
|
59
|
-
- **Generators as the interface** — `
|
|
83
|
+
- **Generators as the interface** — `open_loam:install` and `open_loam:entity` are the
|
|
60
84
|
supported way to add a feature, for humans and agents alike.
|
|
61
85
|
- **Structural guardrails** — tests that fail the build on a missing tenant
|
|
62
86
|
scope, a stray `.unscoped`, or an oversized `AGENTS.md`.
|
|
63
|
-
- **A human-approval gate** — `
|
|
87
|
+
- **A human-approval gate** — `OpenLoam::PendingActions` stages an agent-proposed
|
|
64
88
|
mutation for review before it touches business data.
|
|
65
|
-
- **The golden-tasks benchmark** and `
|
|
89
|
+
- **The golden-tasks benchmark** and `OpenLoam::Eval` scorer. Results, methodology
|
|
66
90
|
and caveats are published at
|
|
67
91
|
<https://deliveristsio.github.io/open-loam/agents/golden-tasks/>; they come
|
|
68
92
|
from a single internal run and are not independently reproduced.
|
|
@@ -74,7 +98,7 @@ used it yet. Evaluate it as a prototype, not as proven infrastructure.
|
|
|
74
98
|
`config.generators` setting, then `bigint`. This covers `create_table`,
|
|
75
99
|
`t.references`, and the polymorphic `*_id` columns that cannot use
|
|
76
100
|
`t.references`.
|
|
77
|
-
- `
|
|
101
|
+
- `OpenLoam::GeneratedKey` assigns a UUID before create when the key is not an
|
|
78
102
|
integer, since a string primary key has no database default. Integer keys are
|
|
79
103
|
still generated by the database.
|
|
80
104
|
|
data/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 Grzegorz Smajdor and
|
|
3
|
+
Copyright (c) 2026 Grzegorz Smajdor and OpenLoam contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenLoam 🌱
|
|
2
2
|
|
|
3
|
-
**In short:**
|
|
3
|
+
**In short:** OpenLoam is a Rails starter kit for business apps — multi-tenancy,
|
|
4
4
|
permissions, audit trails, workflows, and an event bus already built in, so you
|
|
5
5
|
skip months of plumbing and start on real features. It's also AI-native:
|
|
6
6
|
convention-driven code that AI coding agents (Claude Code, Codex, etc.) can
|
|
@@ -12,7 +12,7 @@ safely extend, with human-approval gates before any agent write takes effect.
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
## Why
|
|
15
|
+
## Why OpenLoam exists
|
|
16
16
|
|
|
17
17
|
Every serious back-office app — CRM, ERP, ops console, internal tool — re-derives
|
|
18
18
|
the same ~80%: who's the tenant, who's allowed, what changed and when, how do
|
|
@@ -24,13 +24,13 @@ write real features — *if* the codebase is legible to them. Sprawling, snowfla
|
|
|
24
24
|
architectures confuse agents as much as they confuse new hires. Convention is
|
|
25
25
|
what makes a codebase safe for an agent to extend.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
OpenLoam fuses the two: **a pre-built business foundation that is deliberately
|
|
28
28
|
agent-legible.** Rails already leans convention-over-configuration — arguably the
|
|
29
|
-
most agent-friendly framework there is.
|
|
29
|
+
most agent-friendly framework there is. OpenLoam extends that philosophy from "how to
|
|
30
30
|
structure a controller" up to "how a multi-tenant, permissioned, audited business
|
|
31
31
|
domain is built" — and ships the agent conventions to match.
|
|
32
32
|
|
|
33
|
-
## Where
|
|
33
|
+
## Where OpenLoam sits
|
|
34
34
|
|
|
35
35
|
The pieces exist in Rails, but scattered — foundation shape in Bullet Train and
|
|
36
36
|
the commerce products, an event backbone in Rails Event Store, custom-entity
|
|
@@ -39,7 +39,7 @@ agent-legible business foundation. The closest structural analogs live in other
|
|
|
39
39
|
stacks: Frappe/ERPNext in Python, and
|
|
40
40
|
**[Open Mercato](https://github.com/open-mercato/open-mercato)** in TypeScript —
|
|
41
41
|
whose module system and convention-first, agent-legible approach directly
|
|
42
|
-
inspired
|
|
42
|
+
inspired OpenLoam. OpenLoam brings that idea to Rails, the substrate it always suited.
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
@@ -51,21 +51,21 @@ need to — never a blank page.
|
|
|
51
51
|
| Pillar | What you get, out of the box |
|
|
52
52
|
|--------|------------------------------|
|
|
53
53
|
| 🏢 **Multi-tenancy** | Tenant isolation baked into every query, background job, and event. New models are tenant-scoped by default; a missing tenant context raises, never silently widens a query. |
|
|
54
|
-
| 🔐 **Permissions & auth** | Password login, roles, policies, and field-level write access — declared, not hand-rolled per controller. Tenant selection limited to a user's memberships. Plus **feature-string permissions** (`
|
|
55
|
-
| 🌾 **Custom fields** | Define fields at runtime (a `custom_fields` JSON column + a `
|
|
54
|
+
| 🔐 **Permissions & auth** | Password login, roles, policies, and field-level write access — declared, not hand-rolled per controller. Tenant selection limited to a user's memberships. Plus **feature-string permissions** (`OpenLoam::Permissions`): grant a role wildcard capability strings (`equipment.*`) and check `OpenLoam.can?("equipment.edit")` / `require_permission!` — deny-by-default, a finer layer under the coarse role. |
|
|
55
|
+
| 🌾 **Custom fields** | Define fields at runtime (a `custom_fields` JSON column + a `OpenLoam::FieldDefinition` row), so agents and admins extend a model without a migration for every idea. Filtering and sorting on a custom field is **index-backed** at scale via a typed read-model projection (`OpenLoam::CustomFieldIndex`), not a per-row JSON scan — with **coverage accounting** (is the index complete or drifting?), a read-time gap that **falls back to the authoritative source for correctness** and **self-heals** in the background (deduped), and an honest "results may be incomplete" signal while it does. A field can declare `readable_roles`: filtering or sorting on a field a role **may not read** is refused, so a filter can't become an **inference oracle** on a restricted value. |
|
|
56
56
|
| 🔀 **Workflow** | Declared states, transitions, and role-gated approvals on any entity; each transition emits an event and is audited. |
|
|
57
|
-
| 📡 **Event backbone** | A first-class domain event bus (`domain.thing.happened`, publish/subscribe) so modules stay decoupled and workflows are legible. Two subscriber tiers with a **formal contract**: *ephemeral* (`
|
|
57
|
+
| 📡 **Event backbone** | A first-class domain event bus (`domain.thing.happened`, publish/subscribe) so modules stay decoupled and workflows are legible. Two subscriber tiers with a **formal contract**: *ephemeral* (`OpenLoam::Events.subscribe`, in-process, synchronous, best-effort) for cheap fan-out, and *durable* (`OpenLoam::DurableEvents.register`) which **persists each delivery as a row** in the event's tenant and hands it to a background job — **at-least-once with retry + backoff**, a **dead-letter** view with manual requeue, and a periodic **sweep** that redelivers a lost job (row state, not the queue, is the source of truth). A handler is resolved from a boot-time registry, never constantized from the stored row. |
|
|
58
58
|
| 🔔 **Notifications** | Tenant-scoped in-app notifications, created from events, surfaced in the admin. |
|
|
59
59
|
| 🔌 **API & webhooks** | Token-authenticated JSON API per entity (policy-aware) and per-tenant signed **outbound** webhooks on domain events. **Inbound** webhooks too: a public `/webhooks/:token` receiver that HMAC-verifies each call over the raw body, resists replays (a `(source, delivery-id)` idempotency ledger), answers every auth failure with a uniform `401`, and publishes the verified event onto the bus so durable subscribers react. |
|
|
60
60
|
| 🧾 **Audit** | Every change — who, what, when, in which tenant — recorded by default. |
|
|
61
61
|
| ↩️ **Undo / history** | The audit trail is an **undo stack**: each record's History screen reverts a change with one click, and the undo is itself recorded — so undoing an `undo` is redo. Walks back one step at a time (never clobbers a newer edit); **encrypted fields and workflow state are never reverted** here (state changes undo via the reverse transition). |
|
|
62
62
|
| 🗑️ **Soft-delete** | Deleting a record hides it instead of erasing it — excluded from every query by default, still tenant-scoped in the recycle bin, restorable, and recorded in the audit trail. |
|
|
63
63
|
| ⚙️ **Settings** | A key-value settings store with a global default and a per-tenant override — typed values, resolved override → global → default, cached per request, and never leaking between tenants. |
|
|
64
|
-
| 🚩 **Feature flags** | Runtime on/off capabilities per tenant for rollout or kill-switch — a global default plus per-tenant override, a `
|
|
64
|
+
| 🚩 **Feature flags** | Runtime on/off capabilities per tenant for rollout or kill-switch — a global default plus per-tenant override, a `OpenLoam::Features.on?` guard, and an admin screen. Gates a **capability**, not a person — distinct from roles and policies. |
|
|
65
65
|
| 🔒 **Encryption at rest** | Mark a field `encrypts` and it is transparently AES-256-GCM encrypted with a **per-tenant** key (HKDF, KMS-pluggable) and decrypted on read — a DB dump leaks nothing and tenant A's key never opens tenant B's data. A keyed blind index keeps an encrypted email/phone findable by exact match; the audit trail records the change, never the value. |
|
|
66
66
|
| 🔑 **MFA & step-up auth** | TOTP second factor for admin login (RFC 6238, no dependency), with single-use recovery codes; the secret is encrypted per-user so it verifies in any tenant. `require_sudo!` re-challenges for sensitive actions within a short window — orthogonal to role. MFA can be required per role. Failed password / TOTP / sudo attempts are **rate-limited and locked out** (per-identifier, configurable), so an online brute-force of a 6-digit code is throttled — and a lockout is enumeration-safe (a locked known and unknown identifier respond identically). |
|
|
67
67
|
| 🚦 **AI approval gate** | An agent running under confirm-mode **stages** a write as a `PendingAction` with a before/after preview instead of committing it; a manager approves (a role-gated workflow transition) and only then does it execute — audited to the approver. The human-in-the-loop primitive for agent writes; encrypted fields never appear in the preview or audit. |
|
|
68
|
-
| 🤖 **MCP server** | An [MCP](https://modelcontextprotocol.io) server (`bin/rails
|
|
68
|
+
| 🤖 **MCP server** | An [MCP](https://modelcontextprotocol.io) server (`bin/rails open_loam:mcp:serve`, stdio) exposes OpenLoam to an AI agent: discover entities/schema/policy, **read** tenant-scoped records (only fields the role may see), and **propose** writes that are *staged for human approval* — never committed. Every gate is a OpenLoam gate reused (tenancy, read-ACL, the approval gate); the agent acts as its API token's user, no more. |
|
|
69
69
|
| 👓 **Saved views** | A user names a view of an entity's admin index — filters, sort, columns — and keeps it private, shares it to a role, or makes it the tenant default. Filters only ever touch whitelisted data columns; a stored view is optimistic-locked so shared edits don't clobber. |
|
|
70
70
|
| 🔏 **Concurrent-edit safety** | Optimistic locking (`lock_version`) turns a stale save into a clean "this changed since you opened it" conflict — with a diff and a retry, never a silent clobber — and an advisory `RecordLock` shows "Anna is editing this" with a manager take-over. The version check is the guarantee; the lock is the courtesy. |
|
|
71
71
|
| 📡 **Real-time updates** | A per-tenant Server-Sent-Events stream pushes selected events to the browser — the notification bell increments live, no polling. Opt-in per event pattern (default off, tenant- and audience-filtered), behind a broadcaster seam so Redis/SolidCable drops in for multi-process. |
|
|
@@ -74,24 +74,24 @@ need to — never a blank page.
|
|
|
74
74
|
| 🔎 **Pluggable search** | `searchable_by` and `Model.search(q)` stay put; the strategy behind them is a swappable **driver**. Ships two: a portable substring **LIKE** (default, zero-setup) and a **word-level token index** (order-independent, AND-semantics, still plain SQL — no external service), with the seam ready for Meilisearch/Elasticsearch. Swapping is a one-line initializer change, no call-site edits. Tenant-scoped, and an encrypted field's plaintext is never tokenized. |
|
|
75
75
|
| 🪪 **SSO (OIDC)** | Per-tenant single sign-on: each tenant connects its own identity provider. **Home-realm discovery** routes a user to their IdP by email domain; a verified identity is **just-in-time provisioned** (or linked to an existing account), with IdP group → role mapping. The client secret is encrypted at rest (per-tenant key). An unverified email is refused — no silent account takeover. OIDC ships end-to-end; SAML and SCIM are documented seams behind a protocol interface. |
|
|
76
76
|
| 📚 **Dictionaries** | Per-tenant managed lookup lists — named sets of entries (value/label/color/icon/position/default) an admin curates without a deploy. Usable as a **custom-field type**: a `dictionary` field renders a select of the list's active entries and stores the chosen value, showing its label on read. Tenant-scoped and cached per request. |
|
|
77
|
-
| ⏳ **Task progress** | A long-running job (import, reindex, report) reports percent / counts / ETA to the admin, pushed **live over SSE** — no polling. `
|
|
78
|
-
| 🕰️ **Scheduler** | Per-tenant recurring jobs — cron (`0 7 * * *`) or interval — that enqueue an ActiveJob on schedule. A runner (`
|
|
77
|
+
| ⏳ **Task progress** | A long-running job (import, reindex, report) reports percent / counts / ETA to the admin, pushed **live over SSE** — no polling. `OpenLoam::Progress.start`/`advance`/`complete!`; the browser bar moves as the job runs. The broadcast is throttled to once per whole percent, the job supports a cooperative cancel, and a stalled job (dead heartbeat) is flagged. Tenant-scoped; the frame carries only id/percent/status. |
|
|
78
|
+
| 🕰️ **Scheduler** | Per-tenant recurring jobs — cron (`0 7 * * *`) or interval — that enqueue an ActiveJob on schedule. A runner (`open_loam:scheduler:tick`, wired to system cron) claims due jobs **atomically** (Postgres `SKIP LOCKED`; SQLite a transactional claim), so multiple workers **never double-fire** one. `job_class` is whitelisted to a real ActiveJob (no arbitrary code). Tenant-scope jobs run per tenant; system-scope once. A stdlib cron-next calculator (no gem), timezone-aware. |
|
|
79
79
|
| 📥 **Bulk import / export** | CSV **export** of any entity's current filtered view — policy- and encryption-aware (an encrypted field is redacted, never a plaintext dump). CSV **import** with a column-mapping engine: dedupe by a key (update-or-create), per-row validation with a skipped-row error log and a **downloadable error file**, a **dry-run** that commits nothing, and live progress (backgrounded, reported via the task bar). The mapping only targets policy-permitted fields — no crafted column reaches `tenant_id` or a field a role can't write. Plus **datatable bulk actions** (select rows → soft-delete / set-field / export), policy-checked per record and tenant-scoped. |
|
|
80
80
|
| 📊 **Configurable dashboard** | The admin home is a grid of module-provided **widgets** on a registry — a metric or short list each. A manager picks which widgets appear and in what order, per tenant; a widget's `roles:` filter is enforced server-side (a hidden widget's data is never even computed). Widgets query tenant-scoped models (no cross-tenant leak), and a raising widget is isolated into an error tile — the dashboard never breaks. Ships built-ins (recent activity, unread notifications, pending approvals, running tasks). |
|
|
81
|
-
| 📜 **Auto OpenAPI** | The JSON API documents itself. `
|
|
81
|
+
| 📜 **Auto OpenAPI** | The JSON API documents itself. `OpenLoam::OpenApi` introspects the generated per-entity API controllers — columns/types, exposed fields, custom fields, the bearer-token security scheme, and the tenancy guarantee — into an **OpenAPI 3.1** document, with **no hand-written annotations and no external gem**. A server-rendered explorer at `/admin/api_docs` (no Swagger-UI/external JS), a `.json` endpoint for tooling, and `open_loam:openapi:export` for CI. Request schemas expose only writable fields (never `tenant_id`); encrypted fields are typed as plain strings — the doc describes shape, never data. |
|
|
82
82
|
| 🌐 **Content translations** | Translate the DATA in a record's fields per locale — a product name, a category label — distinct from Rails i18n (developer UI strings, still Rails-native). `translates :name` adds a read-time **overlay**: `record.name` returns the current locale's translation when one exists, else the record's own column (the base value, never lost). Locale is request state (a `/admin` switcher); translations are tenant-scoped, audited, additive rows. An **encrypted field can't be translated** — that would store plaintext, so it's refused at load. |
|
|
83
|
-
| 🧬 **Override registry** | Disable or replace an entry in one of
|
|
83
|
+
| 🧬 **Override registry** | Disable or replace an entry in one of OpenLoam's keyed registries — a built-in dashboard widget, a default broadcast pattern — from an initializer, **without forking or monkeypatching**: `OpenLoam::Overrides.disable(:widgets, "open_progress")` / `.replace(:widgets, "audit_recent") { … }`. The value over a raw monkeypatch: a **stale override** (a key that no longer exists) is warned about at boot, so a typo isn't a silent no-op. Deliberately small — structural pieces (views, controllers, routes) still use standard Rails path-shadowing; this fills the gap for the in-gem registries. |
|
|
84
84
|
| 🖥️ **Admin surface** | An internal console generated from your models — comments, attachments, global search, filtering, pagination — not a second app to maintain. |
|
|
85
85
|
| 🤖 **Agent conventions** | An `AGENTS.md` (byte-budgeted), generators as the one interface, and structural guardrails so an AI agent can add a domain feature **safely** — and a human can read what it did. |
|
|
86
86
|
|
|
87
|
-
You write the **20% that is your business**.
|
|
87
|
+
You write the **20% that is your business**. OpenLoam is the 80% that every business
|
|
88
88
|
app shares.
|
|
89
89
|
|
|
90
90
|
---
|
|
91
91
|
|
|
92
92
|
## Agent-native by design
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
OpenLoam treats "an AI agent will extend this" as a first-class constraint:
|
|
95
95
|
|
|
96
96
|
- **One obvious way** to add an entity, a permission, an event, a screen — so an
|
|
97
97
|
agent's output is predictable and reviewable.
|
|
@@ -111,7 +111,7 @@ for an agent or a human.
|
|
|
111
111
|
|
|
112
112
|
**Working prototype — every pillar in the table above is built, tested, and
|
|
113
113
|
exercised end to end** by a demo app, a generator harness, and an agent
|
|
114
|
-
benchmark. Some two dozen `
|
|
114
|
+
benchmark. Some two dozen `OpenLoam::` modules, from tenancy through SSO, each
|
|
115
115
|
added the same way: a small in-gem implementation behind a convention, wired to
|
|
116
116
|
agree with the rest.
|
|
117
117
|
|
|
@@ -119,7 +119,7 @@ agree with the rest.
|
|
|
119
119
|
|
|
120
120
|
| Path | What it is |
|
|
121
121
|
|------|-----------|
|
|
122
|
-
| `lib/` | The `
|
|
122
|
+
| `lib/` | The `open_loam` gem — every pillar as a small `OpenLoam::` module, plus the `open_loam:install` and `open_loam:entity` generators that are the whole interface. |
|
|
123
123
|
| `demo/` | An equipment-rental app built with those generators (454 tests / 1,534 assertions as of 2026-08-22), carrying the generated guardrail tests: tenant isolation, no-context-raises, a lint against `.unscoped` in `app/`, and a 32 KB `AGENTS.md` budget. |
|
|
124
124
|
| `ai/` | The agent benchmark — `golden_tasks.md` and recorded runs. First run: **10/10 tasks, zero isolation or authorization violations**; a vanilla-Rails control under the same prompts enforced isolation in **1/10**. |
|
|
125
125
|
| `docs/_agents/` | Deep-dive conventions (encryption, SSO, scheduler, …) linked from `AGENTS.md`, so the agent contract stays inside its byte budget. |
|
|
@@ -137,9 +137,9 @@ whole thesis in miniature.
|
|
|
137
137
|
*minimal in-gem implementation* rather than a wrapper around
|
|
138
138
|
`acts_as_tenant`/`pundit`/`paper_trail`/Rails Event Store: the smallest surface
|
|
139
139
|
that proves the conventions and the agent flow. Swapping the proven gems back in
|
|
140
|
-
*behind the same `
|
|
140
|
+
*behind the same `OpenLoam::` conventions* is the roadmap, not a reversal. Custom
|
|
141
141
|
fields use the portable Rails `json` column (not Postgres `jsonb`/GIN) because
|
|
142
|
-
the demo runs on SQLite. See [How
|
|
142
|
+
the demo runs on SQLite. See [How OpenLoam works](docs/_foundation/overview.md) for
|
|
143
143
|
the pillar-by-pillar breakdown and the decisions behind them.
|
|
144
144
|
|
|
145
145
|
**Try it**
|
|
@@ -161,14 +161,14 @@ showing the real commands and exactly what you *didn't* have to write.
|
|
|
161
161
|
- [Getting started](https://deliveristsio.github.io/open-loam/getting-started/) — hands-on, install to first feature
|
|
162
162
|
- [Overview](OVERVIEW.md) — plain-language product, use cases, evidence, and risks
|
|
163
163
|
- [Concept & positioning](https://deliveristsio.github.io/open-loam/concept/)
|
|
164
|
-
- [How
|
|
164
|
+
- [How OpenLoam works](https://deliveristsio.github.io/open-loam/foundation/overview/) — the diagrams above + how every pillar is built
|
|
165
165
|
- [Tenant isolation](https://deliveristsio.github.io/open-loam/foundation/tenant-isolation/) & [Authorization](https://deliveristsio.github.io/open-loam/foundation/authorization/) — the two flagship guarantees, in depth
|
|
166
166
|
- [Agents](https://deliveristsio.github.io/open-loam/agents/) — the agent contract, guardrails, the golden-tasks benchmark, and subsystem deep-dives (encryption, SSO, scheduler, events, inbound webhooks, bulk, confirm-mode)
|
|
167
167
|
- Reference — [configuration](https://deliveristsio.github.io/open-loam/reference/configuration/), [generators](https://deliveristsio.github.io/open-loam/reference/generators/), [backward compatibility](https://deliveristsio.github.io/open-loam/reference/compatibility/)
|
|
168
168
|
- [Roadmap](ROADMAP.md) — ordered backlog, Mercato-informed
|
|
169
169
|
- [Backward-compatibility contract](BACKWARD_COMPATIBILITY.md) — the frozen public surfaces
|
|
170
170
|
- [Architecture decisions](https://deliveristsio.github.io/open-loam/adr/) & [lessons](ai/lessons.md) — why things are the way they are, and the gotchas
|
|
171
|
-
- [Agent pack](.
|
|
171
|
+
- [Agent pack](.open_loam/agents/) — everything an AI agent should load to extend a OpenLoam app correctly
|
|
172
172
|
- [Manifesto](https://deliveristsio.github.io/open-loam/manifesto/)
|
|
173
173
|
- [Contributing](CONTRIBUTING.md)
|
|
174
174
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Heals a custom-field index gap in the background: rebuilds one model's
|
|
3
|
-
# read-model index (
|
|
3
|
+
# read-model index (OpenLoam::CustomFieldIndex) for one tenant. Enqueued (deduped)
|
|
4
4
|
# by CustomFieldIndex when a filter/order runs over an incomplete index, so a
|
|
5
5
|
# gap self-heals without an operator running a rake task. Inherits
|
|
6
6
|
# ActiveJob::Base, not the app's ApplicationRecord-owned base (the gem must not
|
|
@@ -9,14 +9,14 @@ module Loam
|
|
|
9
9
|
queue_as :default
|
|
10
10
|
|
|
11
11
|
def perform(tenant_id, model_name)
|
|
12
|
-
tenant =
|
|
12
|
+
tenant = OpenLoam::Tenant.find_by(id: tenant_id)
|
|
13
13
|
model = model_name.to_s.safe_constantize
|
|
14
|
-
return unless tenant && model.is_a?(Class) && model <
|
|
14
|
+
return unless tenant && model.is_a?(Class) && model < OpenLoam::TenantRecord
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
OpenLoam.as_tenant(tenant) { OpenLoam::CustomFieldIndex.reindex(model) }
|
|
17
17
|
ensure
|
|
18
18
|
# Release the dedup marker so a later real gap can enqueue again.
|
|
19
|
-
|
|
19
|
+
OpenLoam::CustomFieldIndex.clear_pending(tenant_id, model_name.to_s)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
module
|
|
2
|
-
# Runs one durable event delivery (
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# Runs one durable event delivery (OpenLoam::DurableEvents). Inherits
|
|
3
3
|
# ActiveJob::Base rather than the host app's ApplicationJob — the gem must not
|
|
4
4
|
# depend on a class the app owns and may have configured for its own retries —
|
|
5
|
-
# and carries the tenant explicitly (ActiveJob doesn't serialize
|
|
5
|
+
# and carries the tenant explicitly (ActiveJob doesn't serialize OpenLoam::Current).
|
|
6
6
|
#
|
|
7
7
|
# A missing row is an ANSWER, not an error: with an async queue adapter the job
|
|
8
8
|
# can start before the creating transaction commits, so the row isn't visible
|
|
9
9
|
# yet. The job no-ops; the redelivery sweep picks the row up once it commits.
|
|
10
|
-
# Retry state lives in the ROW (
|
|
10
|
+
# Retry state lives in the ROW (OpenLoam::DurableEvents.deliver), never here.
|
|
11
11
|
class EventDeliveryJob < ActiveJob::Base
|
|
12
12
|
queue_as :default
|
|
13
13
|
|
|
14
14
|
def perform(tenant_id, delivery_id)
|
|
15
|
-
tenant =
|
|
15
|
+
tenant = OpenLoam::Tenant.find_by(id: tenant_id)
|
|
16
16
|
return if tenant.nil?
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
delivery =
|
|
18
|
+
OpenLoam.as_tenant(tenant) do
|
|
19
|
+
delivery = OpenLoam::EventDelivery.find_by(id: delivery_id)
|
|
20
20
|
return if delivery.nil? # not visible yet (txn race) or gone — the sweep covers it
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
OpenLoam::DurableEvents.deliver(delivery)
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Periodic durability sweep: re-enqueues due-but-undelivered event deliveries
|
|
3
3
|
# whose accelerator job was lost (worker crash, dropped message, an async
|
|
4
4
|
# adapter racing the creating transaction). This — not perform_later at publish
|
|
5
5
|
# — is what makes persistent delivery durable.
|
|
6
6
|
#
|
|
7
|
-
# Registered per-tenant via
|
|
7
|
+
# Registered per-tenant via OpenLoam::Scheduler (see OpenLoam::Engine), so `sync_tenant`
|
|
8
8
|
# materializes a schedule row per tenant and the scheduler allowlist already
|
|
9
9
|
# covers the class. Tenant-scoped: the scheduler enqueues it with tenant_id and
|
|
10
10
|
# the sweep runs inside that tenant, so there is no cross-tenant scan.
|
|
@@ -12,10 +12,10 @@ module Loam
|
|
|
12
12
|
queue_as :default
|
|
13
13
|
|
|
14
14
|
def perform(tenant_id: nil)
|
|
15
|
-
tenant =
|
|
15
|
+
tenant = OpenLoam::Tenant.find_by(id: tenant_id)
|
|
16
16
|
return if tenant.nil?
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
OpenLoam.as_tenant(tenant) { OpenLoam::DurableEvents.redeliver_stuck }
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
require "net/http"
|
|
2
2
|
require "openssl"
|
|
3
3
|
|
|
4
|
-
module
|
|
4
|
+
module OpenLoam
|
|
5
5
|
# Delivers one event to one endpoint. Inherits ActiveJob::Base rather than
|
|
6
6
|
# the host app's ApplicationJob: the gem must not depend on a class the app
|
|
7
7
|
# owns and may have configured for its own retries.
|
|
8
8
|
#
|
|
9
9
|
# Signing: the receiver recomputes HMAC-SHA256 of the exact body with the
|
|
10
|
-
# endpoint's secret and compares it to X-
|
|
10
|
+
# endpoint's secret and compares it to X-OpenLoam-Signature. Body building and
|
|
11
11
|
# signing are class methods so both sides — and the tests — can call them
|
|
12
12
|
# without a network.
|
|
13
13
|
class WebhookDeliveryJob < ActiveJob::Base
|
|
@@ -24,11 +24,11 @@ module Loam
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def perform(tenant_id, endpoint_id, event_name, payload)
|
|
27
|
-
tenant =
|
|
27
|
+
tenant = OpenLoam::Tenant.find_by(id: tenant_id)
|
|
28
28
|
return if tenant.nil?
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
endpoint =
|
|
30
|
+
OpenLoam.as_tenant(tenant) do
|
|
31
|
+
endpoint = OpenLoam::WebhookEndpoint.find_by(id: endpoint_id)
|
|
32
32
|
# The endpoint may have been deleted or switched off between enqueue
|
|
33
33
|
# and delivery — that is an answer, not an error.
|
|
34
34
|
next if endpoint.nil? || !endpoint.active?
|
|
@@ -49,8 +49,8 @@ module Loam
|
|
|
49
49
|
|
|
50
50
|
request = Net::HTTP::Post.new(uri.request_uri)
|
|
51
51
|
request["Content-Type"] = "application/json"
|
|
52
|
-
request["X-
|
|
53
|
-
request["X-
|
|
52
|
+
request["X-OpenLoam-Event"] = event_name
|
|
53
|
+
request["X-OpenLoam-Signature"] = self.class.signature(endpoint.secret, body)
|
|
54
54
|
request.body = body
|
|
55
55
|
|
|
56
56
|
http.request(request)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A bearer token that lets a machine act as one user in one tenant. Same
|
|
3
3
|
# rules as a human session: whatever the token's user may do in that tenant,
|
|
4
4
|
# no more. Plumbing, so not audited and not evented.
|
|
5
|
-
class ApiToken <
|
|
6
|
-
self.table_name = "
|
|
5
|
+
class ApiToken < OpenLoam::TenantRecord
|
|
6
|
+
self.table_name = "open_loam_api_tokens"
|
|
7
7
|
|
|
8
8
|
belongs_to :user
|
|
9
9
|
|
|
@@ -18,9 +18,9 @@ module Loam
|
|
|
18
18
|
# A bearer token arrives with no tenant context — the token IS how the
|
|
19
19
|
# request discovers which tenant it belongs to, so this one query must
|
|
20
20
|
# bypass the tenant scope. That is exactly what host apps are forbidden to
|
|
21
|
-
# do (`test/
|
|
21
|
+
# do (`test/open_loam_guardrails_test.rb` fails the build on it), so the escape
|
|
22
22
|
# hatch is vetted framework code here, used once, at the edge: find the
|
|
23
|
-
# token, establish
|
|
23
|
+
# token, establish OpenLoam::Current, and everything downstream is ordinary
|
|
24
24
|
# tenant-scoped code again.
|
|
25
25
|
#
|
|
26
26
|
# Returns the token, or nil for an unknown/blank one — callers render 401.
|
|
@@ -30,8 +30,8 @@ module Loam
|
|
|
30
30
|
api_token = unscoped.find_by(token: raw_token)
|
|
31
31
|
return nil unless api_token
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
OpenLoam::Current.tenant = api_token.tenant
|
|
34
|
+
OpenLoam::Current.actor = api_token.user
|
|
35
35
|
api_token.update_column(:last_used_at, Time.current)
|
|
36
36
|
|
|
37
37
|
api_token
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# One row per change: who (actor), what (auditable + action + changeset),
|
|
3
3
|
# when (created_at), in which tenant (tenant_id, via TenantRecord).
|
|
4
4
|
# Append-only by convention; not itself audited or evented.
|
|
5
|
-
class AuditRecord <
|
|
6
|
-
self.table_name = "
|
|
5
|
+
class AuditRecord < OpenLoam::TenantRecord
|
|
6
|
+
self.table_name = "open_loam_audit_records"
|
|
7
7
|
|
|
8
8
|
belongs_to :actor, class_name: "User", optional: true
|
|
9
9
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A single failed authentication attempt, for rate-limiting/lockout
|
|
3
|
-
# (
|
|
3
|
+
# (OpenLoam::AuthThrottle). Deliberately NOT tenant-scoped — authentication happens
|
|
4
4
|
# BEFORE a tenant is chosen (login), so this is a global auth-layer table keyed
|
|
5
|
-
# by the submitted identifier (the same reason
|
|
5
|
+
# by the submitted identifier (the same reason OpenLoam::MfaCredential is global).
|
|
6
6
|
# Allowlisted in the guardrails tenancy lint.
|
|
7
7
|
class AuthAttempt < ApplicationRecord
|
|
8
|
-
include
|
|
9
|
-
self.table_name = "
|
|
8
|
+
include OpenLoam::GeneratedKey
|
|
9
|
+
self.table_name = "open_loam_auth_attempts"
|
|
10
10
|
|
|
11
11
|
validates :identifier, :kind, presence: true
|
|
12
12
|
end
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A per-tenant, admin-configurable rule: WHEN a condition holds THEN run
|
|
3
3
|
# actions. Declared as DATA (a trigger event pattern + a safe condition tree +
|
|
4
|
-
# a typed action list), never as code — see
|
|
4
|
+
# a typed action list), never as code — see OpenLoam::BusinessRules for the engine
|
|
5
5
|
# that evaluates it. Audited like any business record.
|
|
6
|
-
class BusinessRule <
|
|
7
|
-
self.table_name = "
|
|
6
|
+
class BusinessRule < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_business_rules"
|
|
8
8
|
|
|
9
|
-
include
|
|
9
|
+
include OpenLoam::Auditable
|
|
10
10
|
|
|
11
|
-
has_many :runs, class_name: "
|
|
11
|
+
has_many :runs, class_name: "OpenLoam::BusinessRuleRun", dependent: :delete_all
|
|
12
12
|
|
|
13
13
|
validates :name, :trigger, presence: true
|
|
14
14
|
# A rule may only target a tenant-scoped model (or none, for an event-only
|
|
15
15
|
# rule). Refusing a global class like `User` at SAVE time means a poisoned
|
|
16
16
|
# rule can't even be persisted — kept in lockstep with
|
|
17
|
-
#
|
|
17
|
+
# OpenLoam::BusinessRules.subject_for, which refuses the same at run time.
|
|
18
18
|
validate :entity_type_targets_a_tenant_record
|
|
19
19
|
|
|
20
20
|
scope :active, -> { where(active: true) }
|
|
@@ -22,7 +22,7 @@ module Loam
|
|
|
22
22
|
scope :for_entity, ->(entity_type) { where(entity_type: entity_type.to_s) }
|
|
23
23
|
|
|
24
24
|
def matches_trigger?(event_name)
|
|
25
|
-
|
|
25
|
+
OpenLoam::Events.pattern_matches?(trigger, event_name)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# The stored json, defensively — a hand-created row could hold a non-hash /
|
|
@@ -41,8 +41,8 @@ module Loam
|
|
|
41
41
|
return if entity_type.blank? # event-only rule, no subject to load
|
|
42
42
|
|
|
43
43
|
klass = entity_type.safe_constantize
|
|
44
|
-
unless klass.is_a?(Class) && klass <
|
|
45
|
-
errors.add(:entity_type, "must name a
|
|
44
|
+
unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord
|
|
45
|
+
errors.add(:entity_type, "must name a OpenLoam tenant-scoped model (not #{entity_type.inspect})")
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# The log of why a business rule did (or did not) act — so the admin can see
|
|
3
|
-
# WHY something happened. Plumbing, like
|
|
3
|
+
# WHY something happened. Plumbing, like OpenLoam::RecordLock: NOT audited (a log
|
|
4
4
|
# of a log is noise). Capped per rule so the table stays lean.
|
|
5
|
-
class BusinessRuleRun <
|
|
6
|
-
self.table_name = "
|
|
5
|
+
class BusinessRuleRun < OpenLoam::TenantRecord
|
|
6
|
+
self.table_name = "open_loam_business_rule_runs"
|
|
7
7
|
|
|
8
8
|
KEEP_PER_RULE = 50
|
|
9
9
|
|
|
10
|
-
belongs_to :business_rule, class_name: "
|
|
10
|
+
belongs_to :business_rule, class_name: "OpenLoam::BusinessRule"
|
|
11
11
|
|
|
12
12
|
scope :recent, -> { order(created_at: :desc) }
|
|
13
13
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A note by one person, on one record, in one tenant. Polymorphic, so any
|
|
3
|
-
# entity that `include
|
|
3
|
+
# entity that `include OpenLoam::Commentable` gets a discussion for free.
|
|
4
4
|
#
|
|
5
5
|
# Not audited on purpose: the comment IS the activity record. A
|
|
6
|
-
#
|
|
6
|
+
# OpenLoam::AuditRecord saying "someone created a comment" beside a comment
|
|
7
7
|
# saying what they wrote is the same fact stored twice.
|
|
8
8
|
#
|
|
9
|
-
# Evented on purpose, in
|
|
9
|
+
# Evented on purpose, in OpenLoam's own domain ("open_loam.comment.created"), because
|
|
10
10
|
# a new comment is exactly the kind of thing an app wants to react to —
|
|
11
11
|
# notify the watchers, ping a webhook — without every app inventing its own
|
|
12
12
|
# event name for it.
|
|
13
|
-
class Comment <
|
|
14
|
-
include
|
|
13
|
+
class Comment < OpenLoam::TenantRecord
|
|
14
|
+
include OpenLoam::Eventful
|
|
15
15
|
|
|
16
|
-
self.table_name = "
|
|
16
|
+
self.table_name = "open_loam_comments"
|
|
17
17
|
|
|
18
|
-
event_domain :
|
|
19
|
-
event_entity :comment # not "
|
|
18
|
+
event_domain :open_loam
|
|
19
|
+
event_entity :comment # not "open_loam_comment", which the namespace would give
|
|
20
20
|
|
|
21
21
|
belongs_to :commentable, polymorphic: true
|
|
22
22
|
belongs_to :author, class_name: "User"
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A single configuration setting: one namespaced key, one JSON-able value.
|
|
3
3
|
#
|
|
4
|
-
# Deliberately NOT a
|
|
4
|
+
# Deliberately NOT a OpenLoam::TenantRecord. A setting exists at two levels — a
|
|
5
5
|
# GLOBAL default (tenant_id NULL) that every tenant sees, and a per-tenant
|
|
6
6
|
# OVERRIDE (tenant_id set) that wins for that one tenant — so tenancy is a
|
|
7
7
|
# nullable column here, not a default_scope. Resolving a key means reading
|
|
8
8
|
# across both levels at once, which is why the lookup lives in vetted gem code
|
|
9
|
-
# (
|
|
10
|
-
# test/
|
|
9
|
+
# (OpenLoam::Configs), never in app code (see the tenancy allowlist in
|
|
10
|
+
# test/open_loam_guardrails_test.rb).
|
|
11
11
|
#
|
|
12
12
|
# The value goes in a json column, so a bool, number, string, array, or hash
|
|
13
13
|
# all round-trip under the same key. Row existence — not the value — is what
|
|
14
14
|
# marks a key as set, so the value may legitimately be JSON null.
|
|
15
15
|
class Config < ApplicationRecord
|
|
16
|
-
include
|
|
17
|
-
self.table_name = "
|
|
16
|
+
include OpenLoam::GeneratedKey
|
|
17
|
+
self.table_name = "open_loam_configs"
|
|
18
18
|
|
|
19
|
-
belongs_to :tenant, class_name: "
|
|
19
|
+
belongs_to :tenant, class_name: "OpenLoam::Tenant", optional: true
|
|
20
20
|
|
|
21
21
|
validates :key, presence: true
|
|
22
22
|
# One global per key, one override per key per tenant. AR renders the nil
|