ecoportal-api-graphql 1.3.5 → 1.3.9
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/.ai-assistance/bridge/CLAUDE.md +338 -0
- data/.ai-assistance/bridge/archive/.gitkeep +0 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +29 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +18 -0
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +42 -0
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +115 -0
- data/.ai-assistance/bridge/context/gemini-review-prompt.txt +48 -0
- data/.ai-assistance/bridge/context/gemini-review-response.md +104 -0
- data/.ai-assistance/bridge/context/project.md +42 -0
- data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/request-for-standards-discovery.md +48 -0
- data/.ai-assistance/bridge/queue/.gitkeep +1 -0
- data/.ai-assistance/capabilities/CLAUDE.md +27 -0
- data/.ai-assistance/capabilities/assumptions-log.md +80 -0
- data/.ai-assistance/capabilities/code.md +47 -0
- data/.ai-assistance/capabilities/connectors.md +37 -0
- data/.ai-assistance/capabilities/cowork.md +55 -0
- data/.ai-assistance/code/OVERVIEW.md +155 -0
- data/.ai-assistance/code/data_fields.md +242 -0
- data/.ai-assistance/code/dependencies.md +151 -0
- data/.ai-assistance/code/diff_as_input.md +234 -0
- data/.ai-assistance/code/diff_service_deep_dive.md +192 -0
- data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +55 -0
- data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +181 -0
- data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +192 -0
- data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +147 -0
- data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +277 -0
- data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +200 -0
- data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +228 -0
- data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +197 -0
- data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +243 -0
- data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +196 -0
- data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +132 -0
- data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +187 -0
- data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +236 -0
- data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +183 -0
- data/.ai-assistance/code/ecoportal_schema_reference.md +240 -0
- data/.ai-assistance/code/graphql_domain_knowledge.md +230 -0
- data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +71 -0
- data/.ai-assistance/code/refactoring/opportunities.md +251 -0
- data/.ai-assistance/code/schema_analysis.md +321 -0
- data/.ai-assistance/code/search_filters.md +868 -0
- data/.ai-assistance/code/spec_coverage.md +73 -0
- data/.ai-assistance/code/workflow-command-guide.md +438 -0
- data/.ai-assistance/code/workflow-space.md +353 -0
- data/.ai-assistance/conventions/CLAUDE.md +30 -0
- data/.ai-assistance/conventions/code-working-tree-protocol.md +199 -0
- data/.ai-assistance/conventions/gitignore-rules.md +42 -0
- data/.ai-assistance/conventions/permission-guidance.md +120 -0
- data/.ai-assistance/integrations/README.md +70 -0
- data/.ai-assistance/integrations/gitkraken-mcp.md +107 -0
- data/.ai-assistance/integrations/gitlab-mcp.md +123 -0
- data/.ai-assistance/integrations/local-git.md +60 -0
- data/.ai-assistance/local_paths.example.md +17 -0
- data/.ai-assistance/projects/TODO.md +97 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +168 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +60 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +267 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +53 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +102 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +139 -0
- data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +93 -0
- data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +244 -0
- data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +266 -0
- data/.ai-assistance/projects/eco-helpers-compat/TODO.md +86 -0
- data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +101 -0
- data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +177 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +161 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +125 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +126 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +256 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +122 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +148 -0
- data/.ai-assistance/projects/page-model/DECISIONS.md +245 -0
- data/.ai-assistance/projects/page-model/TODO.md +190 -0
- data/.ai-assistance/projects/search-filter-builder/INTENT.md +107 -0
- data/.ai-assistance/projects/search-filter-builder/TODO.md +131 -0
- data/.ai-assistance/projects/template-maintenance/DESIGN.md +134 -0
- data/.ai-assistance/projects/workflow-space/TODO.md +213 -0
- data/.ai-assistance/reinstall-claude-desktop-windows.md +136 -0
- data/.ai-assistance/scripts/CLAUDE.md +150 -0
- data/.ai-assistance/scripts/bridge-init.sh +86 -0
- data/.ai-assistance/scripts/bridge-status.sh +44 -0
- data/.ai-assistance/scripts/capabilities-check.ts +104 -0
- data/.ai-assistance/scripts/check-outbox.sh +43 -0
- data/.ai-assistance/scripts/dep_graph.rb +91 -0
- data/.ai-assistance/scripts/lock-acquire.sh +103 -0
- data/.ai-assistance/scripts/lock-multi.sh +124 -0
- data/.ai-assistance/scripts/lock-queue.sh +94 -0
- data/.ai-assistance/scripts/setup-mcps.test.ts +188 -0
- data/.ai-assistance/scripts/setup-mcps.ts +234 -0
- data/.ai-assistance/scripts/task-complete.ts +74 -0
- data/.ai-assistance/scripts/task-create.ts +75 -0
- data/.ai-assistance/scripts/task-read.ts +125 -0
- data/.ai-assistance/scripts/token-logger.js +220 -0
- data/.ai-assistance/scripts/token-report.ts +158 -0
- data/.ai-assistance/scripts/token-session-start.js +66 -0
- data/.ai-assistance/skills/ai-instructions/SKILL.md +48 -0
- data/.ai-assistance/skills/code-specs/SKILL.md +69 -0
- data/.ai-assistance/skills/corporate-policies/SKILL.md +201 -0
- data/.ai-assistance/skills/dep-graph/SKILL.md +139 -0
- data/.ai-assistance/skills/ep-ai-manager/SKILL.md +417 -0
- data/.ai-assistance/skills/gemini-assist/SKILL.md +63 -0
- data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +205 -0
- data/.ai-assistance/skills/gemini-assist/gemini_ask.py +1 -0
- data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +240 -0
- data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +25 -0
- data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +261 -0
- data/.ai-assistance/skills/project-cycle/SKILL.md +177 -0
- data/.ai-assistance/skills/refactor/SKILL.md +62 -0
- data/.ai-assistance/skills/rubocop/SKILL.md +93 -0
- data/.ai-assistance/skills/ruby-scripting/SKILL.md +215 -0
- data/.ai-assistance/skills/spec-generation/SKILL.md +72 -0
- data/.ai-assistance/standards-version.json +21 -0
- data/.ai-assistance/token-budget.json +32 -0
- data/.ai-assistance/version.json +39 -0
- data/.claude/settings.json +146 -0
- data/.env.example +18 -0
- data/.gitattributes +15 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +121 -97
- data/CHANGELOG.md +673 -477
- data/CLAUDE.md +232 -0
- data/Gemfile +30 -6
- data/Rakefile +90 -38
- data/docs/worklog.md +574 -0
- data/ecoportal-api-graphql.gemspec +40 -40
- data/lib/ecoportal/api/common/graphql/CLAUDE.md +36 -0
- data/lib/ecoportal/api/common/graphql/client.rb +1 -1
- data/lib/ecoportal/api/common/graphql/http_client.rb +35 -3
- data/lib/ecoportal/api/common/graphql/model/CLAUDE.md +28 -0
- data/lib/ecoportal/api/common/graphql/model/as_input.rb +8 -5
- data/lib/ecoportal/api/common/graphql/model/diffable/classic_diff_service.rb +3 -1
- data/lib/ecoportal/api/common/graphql/model/diffable/diff_service.rb +31 -23
- data/lib/ecoportal/api/common/graphql/model/diffable/hash_diff_nesting.rb +54 -1
- data/lib/ecoportal/api/graphql/CLAUDE.md +37 -0
- data/lib/ecoportal/api/graphql/base/CLAUDE.md +50 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -0
- data/lib/ecoportal/api/graphql/base/delta_result.rb +16 -0
- data/lib/ecoportal/api/graphql/base/force/binding.rb +19 -0
- data/lib/ecoportal/api/graphql/base/force/binding_collection.rb +62 -0
- data/lib/ecoportal/api/graphql/base/force/collection.rb +47 -0
- data/lib/ecoportal/api/graphql/base/force.rb +65 -0
- data/lib/ecoportal/api/graphql/base/kickstand/job.rb +17 -0
- data/lib/ecoportal/api/graphql/base/kickstand/workflow.rb +18 -0
- data/lib/ecoportal/api/graphql/base/kickstand.rb +13 -0
- data/lib/ecoportal/api/graphql/base/page/basic.rb +38 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/actions_list.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/ai_summary.rb +18 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/checklist.rb +29 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/collection.rb +112 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +28 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/cross_reference.rb +54 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/date_field.rb +23 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +25 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/gauge.rb +19 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/geo.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/law.rb +8 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/mailbox.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/number.rb +20 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/people.rb +35 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/plain_text.rb +17 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/rich_text.rb +26 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/select.rb +41 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/signature.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/smart_fill.rb +17 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/table.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/tag_field.rb +21 -0
- data/lib/ecoportal/api/graphql/base/page/data_field.rb +137 -12
- data/lib/ecoportal/api/graphql/base/page/phased/stage.rb +68 -14
- data/lib/ecoportal/api/graphql/base/page/phased.rb +1 -0
- data/lib/ecoportal/api/graphql/base/page/section.rb +36 -0
- data/lib/ecoportal/api/graphql/base/page/section_collection.rb +79 -0
- data/lib/ecoportal/api/graphql/base/page.rb +2 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/action_type_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/callback_type.rb +28 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_change.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_change_message.rb +15 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_es_change.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_interface.rb +36 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/email_config.rb +18 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/escalation_level.rb +19 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/in_system_config.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/mailbox_field_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operation_interface.rb +39 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/assign_to.rb +27 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/create_page.rb +21 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/send_notification.rb +30 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/people_field_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/recipient_config.rb +34 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/register_field.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/task_config_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/time_delay_config.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/trigger_interface.rb +26 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/triggers/conditional_logic.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/user_selection.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow.rb +35 -0
- data/lib/ecoportal/api/graphql/base/preset_view.rb +17 -0
- data/lib/ecoportal/api/graphql/base/preview_page.rb +23 -0
- data/lib/ecoportal/api/graphql/base/register.rb +18 -0
- data/lib/ecoportal/api/graphql/base.rb +11 -0
- data/lib/ecoportal/api/graphql/builder/CLAUDE.md +65 -0
- data/lib/ecoportal/api/graphql/builder/kickstand.rb +73 -0
- data/lib/ecoportal/api/graphql/builder/page.rb +210 -41
- data/lib/ecoportal/api/graphql/builder/register/preset_view.rb +84 -0
- data/lib/ecoportal/api/graphql/builder/register.rb +27 -19
- data/lib/ecoportal/api/graphql/builder/template.rb +80 -0
- data/lib/ecoportal/api/graphql/builder.rb +2 -0
- data/lib/ecoportal/api/graphql/compat/filter_translator.rb +107 -0
- data/lib/ecoportal/api/graphql/compat/page_reference.rb +23 -0
- data/lib/ecoportal/api/graphql/compat/pages.rb +212 -0
- data/lib/ecoportal/api/graphql/compat/registers.rb +84 -0
- data/lib/ecoportal/api/graphql/compat/response.rb +35 -0
- data/lib/ecoportal/api/graphql/compat/search_results.rb +33 -0
- data/lib/ecoportal/api/graphql/compat/stage_collection.rb +70 -0
- data/lib/ecoportal/api/graphql/compat/stage_view.rb +76 -0
- data/lib/ecoportal/api/graphql/compat.rb +17 -0
- data/lib/ecoportal/api/graphql/concerns/data_field_access.rb +71 -0
- data/lib/ecoportal/api/graphql/concerns/deprecation.rb +20 -0
- data/lib/ecoportal/api/graphql/concerns/fragment_definitions.rb +21 -28
- data/lib/ecoportal/api/graphql/concerns/page_compat.rb +51 -0
- data/lib/ecoportal/api/graphql/concerns/snake_camel_access.rb +60 -0
- data/lib/ecoportal/api/graphql/concerns.rb +4 -0
- data/lib/ecoportal/api/graphql/connection/page.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/pages_workflow_command.rb +13 -0
- data/lib/ecoportal/api/graphql/connection/preset_view.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -0
- data/lib/ecoportal/api/graphql/connection.rb +4 -0
- data/lib/ecoportal/api/graphql/file_upload/client.rb +181 -0
- data/lib/ecoportal/api/graphql/file_upload.rb +10 -0
- data/lib/ecoportal/api/graphql/fragment/action.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/action_category.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/contractor_entity.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/force.rb +30 -0
- data/lib/ecoportal/api/graphql/fragment/location_draft.rb +2 -2
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/locations_error.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/page.rb +85 -0
- data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +395 -0
- data/lib/ecoportal/api/graphql/fragment/pages.rb +15 -0
- data/lib/ecoportal/api/graphql/fragment/pages_workflow.rb +172 -0
- data/lib/ecoportal/api/graphql/fragment/pagination.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment.rb +37 -27
- data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +25 -0
- data/lib/ecoportal/api/graphql/input/delta_input.rb +16 -0
- data/lib/ecoportal/api/graphql/input/page/archive.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/build_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/create_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/create_from_template.rb +18 -0
- data/lib/ecoportal/api/graphql/input/page/delete_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/publish_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/review_task.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/unarchive.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/update.rb +140 -0
- data/lib/ecoportal/api/graphql/input/page.rb +26 -0
- data/lib/ecoportal/api/graphql/input/preset_view/create.rb +18 -0
- data/lib/ecoportal/api/graphql/input/preset_view/permission.rb +16 -0
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -0
- data/lib/ecoportal/api/graphql/input/preset_view.rb +14 -0
- data/lib/ecoportal/api/graphql/input/register/create.rb +18 -0
- data/lib/ecoportal/api/graphql/input/register/update.rb +15 -0
- data/lib/ecoportal/api/graphql/input/register.rb +13 -0
- data/lib/ecoportal/api/graphql/input/search_conf/ai_generator.rb +234 -0
- data/lib/ecoportal/api/graphql/input/search_conf.rb +367 -0
- data/lib/ecoportal/api/graphql/input/variable_binding.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_action_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_comment_tagging_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_default_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_gauge_field_stop.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_field_config.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_action_type.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_filter.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_people_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_task_config.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback_action.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_select_field_option.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_task.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_task_assignment_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_workflow_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/collapse_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_creator_permissions.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_gauge_field_stop.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_field_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_reminder.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_required_sign_offs.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_comment_tagging.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_task_assignment.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_scheduled_callback.rb +22 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_section_header.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_select_field_option.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_task_due.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_trigger.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/expand_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/contractor_entities.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/gauge.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/location_field.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/signature.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/table.rb +25 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/move_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_action_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_comment_tagging_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_gauge_field_stop.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_field_config.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_action_type.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_filter.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_people_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_task_config.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_scheduled_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_select_field_option.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_assignment_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_due.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_priority_level.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -0
- data/lib/ecoportal/api/graphql/input.rb +8 -0
- data/lib/ecoportal/api/graphql/interface/base_page.rb +100 -58
- data/lib/ecoportal/api/graphql/interface/location_structure/nodes.rb +27 -28
- data/lib/ecoportal/api/graphql/logic/base_model.rb +2 -0
- data/lib/ecoportal/api/graphql/logic/base_query.rb +45 -2
- data/lib/ecoportal/api/graphql/logic/input.rb +15 -0
- data/lib/ecoportal/api/graphql/model/ai_summary_version.rb +10 -0
- data/lib/ecoportal/api/graphql/model/organization.rb +65 -55
- data/lib/ecoportal/api/graphql/model/page/basic.rb +14 -0
- data/lib/ecoportal/api/graphql/model/page/phased.rb +82 -20
- data/lib/ecoportal/api/graphql/model/page.rb +1 -0
- data/lib/ecoportal/api/graphql/model/page_union.rb +21 -0
- data/lib/ecoportal/api/graphql/model/pages_workflow.rb +20 -0
- data/lib/ecoportal/api/graphql/model/preset_view.rb +10 -0
- data/lib/ecoportal/api/graphql/model/preview_page.rb +10 -0
- data/lib/ecoportal/api/graphql/model/register.rb +10 -0
- data/lib/ecoportal/api/graphql/model.rb +8 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary.rb +13 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_jobs.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_workflows.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/fail_job.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/fail_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/start_job.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/start_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/stop_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand.rb +18 -0
- data/lib/ecoportal/api/graphql/mutation/location_structure/apply_commands.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/add_commands.rb +2 -2
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/create.rb +2 -2
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/drop_bad_commands.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/publish.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/archive.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/build_from_template.rb +50 -0
- data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/create_from_template.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +69 -0
- data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +51 -0
- data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/unarchive.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/update.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/update_variable_bindings.rb +44 -0
- data/lib/ecoportal/api/graphql/mutation/page.rb +28 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/create.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/permission.rb +37 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/update.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view.rb +15 -0
- data/lib/ecoportal/api/graphql/mutation/register/create.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register/update.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register.rb +14 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -0
- data/lib/ecoportal/api/graphql/mutation/template/create.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -0
- data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template/publish.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/unpublish.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/update.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template.rb +18 -0
- data/lib/ecoportal/api/graphql/mutation.rb +8 -0
- data/lib/ecoportal/api/graphql/payload/ai_summary_generate.rb +12 -0
- data/lib/ecoportal/api/graphql/payload/execute_workflow_commands.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/force_commands.rb +31 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_jobs.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_workflows.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/job.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/workflow.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/kickstand.rb +15 -0
- data/lib/ecoportal/api/graphql/payload/location_structure/draft/create.rb +33 -34
- data/lib/ecoportal/api/graphql/payload/ok_payload.rb +21 -0
- data/lib/ecoportal/api/graphql/payload/page/archive.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/build_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/create_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/draft.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/unarchive.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/update.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/update_variable_bindings.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page.rb +19 -0
- data/lib/ecoportal/api/graphql/payload/preset_view.rb +11 -0
- data/lib/ecoportal/api/graphql/payload/register.rb +11 -0
- data/lib/ecoportal/api/graphql/payload/template/create.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/publish.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/unpublish.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/update.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/update_information.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template.rb +18 -0
- data/lib/ecoportal/api/graphql/payload.rb +11 -0
- data/lib/ecoportal/api/graphql/query/action.rb +1 -1
- data/lib/ecoportal/api/graphql/query/action_categories.rb +1 -1
- data/lib/ecoportal/api/graphql/query/actions.rb +2 -2
- data/lib/ecoportal/api/graphql/query/contractor_entities.rb +1 -1
- data/lib/ecoportal/api/graphql/query/file_upload_signature.rb +76 -0
- data/lib/ecoportal/api/graphql/query/location_structure/draft.rb +2 -2
- data/lib/ecoportal/api/graphql/query/location_structure.rb +1 -1
- data/lib/ecoportal/api/graphql/query/location_structures.rb +1 -1
- data/lib/ecoportal/api/graphql/query/page.rb +45 -0
- data/lib/ecoportal/api/graphql/query/page_delta.rb +47 -0
- data/lib/ecoportal/api/graphql/query/page_with_forces.rb +43 -0
- data/lib/ecoportal/api/graphql/query/pages.rb +59 -0
- data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +59 -0
- data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -0
- data/lib/ecoportal/api/graphql/query/register_preview_pages.rb +83 -0
- data/lib/ecoportal/api/graphql/query/templates.rb +53 -0
- data/lib/ecoportal/api/graphql/query.rb +11 -0
- data/lib/ecoportal/api/graphql.rb +60 -2
- data/lib/ecoportal/api/graphql_version.rb +5 -5
- data/scripts/auto-worker-scheduler.sh +386 -0
- data/tests/contractor_entity_create.rb +19 -19
- data/tests/contractor_entity_udpate.rb +20 -20
- data/tests/dump_page_model.rb +74 -0
- data/tests/loc_structure_get.rb +1 -2
- data/tests/loc_structure_update.rb +51 -51
- data/tests/loc_structures_get.rb +15 -15
- metadata +436 -5
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Code Spec: DiffService Deep Dive
|
|
2
|
+
|
|
3
|
+
**Scope:** `DiffService`, `ClassicDiffService`, `HashDiffNesting`, `AsInput` — bugs discovered
|
|
4
|
+
during 4.4 spec work, current state, and what still needs fixing.
|
|
5
|
+
**Last updated:** 2026-06-05
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Current State (post-4.2 / pre-4.4 completion)
|
|
10
|
+
|
|
11
|
+
### What works
|
|
12
|
+
- **Flat diffs on simple models** (no cascade): `change_data` produces correct output
|
|
13
|
+
after key normalisation fix (see below).
|
|
14
|
+
- **`patchVer` injection** in `AsInput#as_input`.
|
|
15
|
+
- **`from_model` delegation** via `Logic::Input.from_model`.
|
|
16
|
+
- **`ContractorEntity::Update.from_model`** IdDiff reshaping.
|
|
17
|
+
|
|
18
|
+
### What is still broken / incomplete
|
|
19
|
+
See TODO 4.4 for the spec gaps. Key issues:
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Bug 1 — String vs Symbol Keys in `change_data` (ROOT CAUSE OF MOST FAILURES)
|
|
24
|
+
|
|
25
|
+
`ClassicDiffService#prev_doc` returns `model.original_doc` — a hash with **string keys**
|
|
26
|
+
(set via `JSON.parse` in `DoubleModel#initialize`).
|
|
27
|
+
|
|
28
|
+
`HashDiffNesting#change_data` converts each key to symbol via `key.to_sym` and then
|
|
29
|
+
looks up the value in `b` (prev_doc) with:
|
|
30
|
+
```ruby
|
|
31
|
+
if b&.key?(key) # key is now :name (symbol)
|
|
32
|
+
b_value = b[key]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Since `b` has `'name'` (string), `b.key?(:name)` is **always false**. Every field appears
|
|
36
|
+
as "changed" (new value vs nil previous). The diff is wrong.
|
|
37
|
+
|
|
38
|
+
### Fix needed in `DiffService#classic_diff`
|
|
39
|
+
|
|
40
|
+
Normalise both docs to symbol keys before comparison:
|
|
41
|
+
```ruby
|
|
42
|
+
def classic_diff(flat: flat?, ignore: [])
|
|
43
|
+
curr = keys_to_sym_deep(curr_doc(flat: flat) || {})
|
|
44
|
+
prev = keys_to_sym_deep(prev_doc(flat: flat) || {})
|
|
45
|
+
|
|
46
|
+
data = change_data(curr, prev, ignore: to_a(ignored, ignore))
|
|
47
|
+
return nil if data == NO_CHANGES
|
|
48
|
+
|
|
49
|
+
id = get_id(curr, exception: false)
|
|
50
|
+
id ? { id: id }.merge(data) : data
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`keys_to_sym_deep` is available as an instance method via `include HashHelpers`.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Bug 2 — `dig_path?` Returns True for Any Single-Element Path
|
|
59
|
+
|
|
60
|
+
The `DoubleModel::HashHelpers` implementation:
|
|
61
|
+
```ruby
|
|
62
|
+
def dig_path?(obj, keys)
|
|
63
|
+
return false unless obj.respond_to?(:[])
|
|
64
|
+
return true if keys.length == 1 # ← BUG: doesn't check key existence
|
|
65
|
+
dig_path?(obj[keys.first], keys[1..])
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This always returns `true` for single-element key paths regardless of whether the key
|
|
70
|
+
actually exists. For `diff_reduce`, this causes:
|
|
71
|
+
- `dig_path?({id:, name:}, ['actionCategory'])` → **true** (wrong! actionCategory not present)
|
|
72
|
+
- `dig_set!(result, ['actionCategory'], nil)` adds spurious nil entries to the diff
|
|
73
|
+
|
|
74
|
+
**Fix in `HashDiffNesting#dig_path?`:**
|
|
75
|
+
```ruby
|
|
76
|
+
def dig_path?(obj, keys)
|
|
77
|
+
return false unless obj.is_a?(Hash)
|
|
78
|
+
return obj.key?(keys.first) if keys.length == 1 # check actual existence
|
|
79
|
+
return false unless obj.key?(keys.first)
|
|
80
|
+
dig_path?(obj[keys.first], keys[1..])
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Bug 3 — `diff_reduce` Cannot Add New Cascade Keys
|
|
87
|
+
|
|
88
|
+
`diff_reduce` was designed to REPLACE existing cascade keys in the flat diff. But
|
|
89
|
+
cascade attributes are always STRIPPED from the flat diff (`doc_with_non_cascaded_attributes`).
|
|
90
|
+
So the keys are never in the flat diff, and `diff_reduce` never adds them.
|
|
91
|
+
|
|
92
|
+
### Current broken flow for nested change only:
|
|
93
|
+
1. `dueDate` changed on an Action, nothing else changed
|
|
94
|
+
2. `classic_diff(flat: true)` → `nil` (dueDate stripped, no flat-level changes)
|
|
95
|
+
3. `diff_reduce(nil)` → all `dig_path?(nil, ...)` return `false` → cascade never processed
|
|
96
|
+
4. Result: `nil` — the nested change is LOST
|
|
97
|
+
|
|
98
|
+
### Current broken flow for mixed change:
|
|
99
|
+
1. `name` changed AND `dueDate` changed
|
|
100
|
+
2. `classic_diff(flat: true)` → `{id:, name: 'Updated'}` (flat change only)
|
|
101
|
+
3. `diff_reduce({id:, name: 'Updated'})`:
|
|
102
|
+
- `dig_path?({id:, name:}, ['dueDate'])` → `false` (dueDate not in flat diff) → skip
|
|
103
|
+
4. Result: `{id:, name: 'Updated'}` — dueDate change LOST
|
|
104
|
+
|
|
105
|
+
### Fix needed in `diff_reduce`:
|
|
106
|
+
|
|
107
|
+
Remove the `dig_path?` gate. Instead, ADD cascade diffs directly:
|
|
108
|
+
```ruby
|
|
109
|
+
def diff_reduce(init, ignore: [])
|
|
110
|
+
subject.cascaded_reduce(init, recurs: false) do |result, obj, _key, key_path, _trace|
|
|
111
|
+
next result if key_path.empty?
|
|
112
|
+
next result if obj == subject
|
|
113
|
+
next result if root?(obj)
|
|
114
|
+
next result unless obj.is_a?(Ecoportal::API::Common::GraphQL::Model)
|
|
115
|
+
|
|
116
|
+
value = obj.as_update(ignore: ignore)
|
|
117
|
+
next result if value.nil?
|
|
118
|
+
|
|
119
|
+
result ||= {}
|
|
120
|
+
dig_set!(result, key_path, value)
|
|
121
|
+
result
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Additionally, `diff` must ensure `id:` is present even when init was nil:
|
|
127
|
+
```ruby
|
|
128
|
+
def diff(flat: flat?, ignore: [])
|
|
129
|
+
flat_result = classic_diff(flat: true, ignore: ignore)
|
|
130
|
+
return flat_result if flat
|
|
131
|
+
|
|
132
|
+
result = diff_reduce(flat_result, ignore: ignore)
|
|
133
|
+
return nil if result.nil?
|
|
134
|
+
|
|
135
|
+
# Guarantee id: is in result when cascade added something
|
|
136
|
+
result[:id] ||= get_id(keys_to_sym_deep(curr_doc(flat: true) || {}), exception: false)
|
|
137
|
+
result
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Bug 4 — `ecoportal-api-v2` `_cascaded_attributes_trace` (FIXED in 6a2b1b5)
|
|
144
|
+
|
|
145
|
+
Block params were `|out, (attribute, obj_k)|` (swapped) — `attribute` received the
|
|
146
|
+
trace hash instead of the attribute symbol, causing `TypeError: Hash is not a symbol`.
|
|
147
|
+
Fixed to `|(attribute, obj_k), out|`. Requires rebuild of ecoportal-api-v2 gem.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Missing Methods in `DiffService` (FIXED in 0b68a6b)
|
|
152
|
+
|
|
153
|
+
`diff_reduce` calls `dig_path?`, `dig_delete!`, `dig_set!` which are defined in
|
|
154
|
+
`DoubleModel::HashHelpers` (ecoportal-api-v2) but NOT included in `DiffService`.
|
|
155
|
+
|
|
156
|
+
Added these three methods to `HashDiffNesting::InstanceMethods`. See
|
|
157
|
+
`lib/ecoportal/api/common/graphql/model/diffable/hash_diff_nesting.rb`.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## `diff_reduce` — ArrayModel Safety (FIXED in 0b68a6b)
|
|
162
|
+
|
|
163
|
+
`passarray :fieldName` creates `ArrayModel` subclasses inheriting from `DoubleModel`.
|
|
164
|
+
These respond to `as_update` but use the ecoportal-api-v2 signature (no kwargs).
|
|
165
|
+
|
|
166
|
+
Added guard: `next result unless obj.is_a?(Ecoportal::API::Common::GraphQL::Model)`.
|
|
167
|
+
This limits cascade-diffing to GraphQL model objects only.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Recommended Implementation Order for 4.4 Completion
|
|
172
|
+
|
|
173
|
+
1. Fix `DiffService#classic_diff` key normalisation (Bug 1 — most impactful)
|
|
174
|
+
2. Fix `HashDiffNesting#dig_path?` to check key existence (Bug 2)
|
|
175
|
+
3. Redesign `diff_reduce` to ADD rather than REPLACE cascade keys (Bug 3)
|
|
176
|
+
4. Update `diff` to ensure `id:` presence and proper no-change return
|
|
177
|
+
5. Update specs to pass for all scenarios
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## File Locations
|
|
182
|
+
|
|
183
|
+
| File | Role |
|
|
184
|
+
|------|------|
|
|
185
|
+
| `lib/.../model/diffable/diff_service.rb` | Main DiffService — `classic_diff`, `diff`, `diff_reduce` |
|
|
186
|
+
| `lib/.../model/diffable/classic_diff_service.rb` | Base: `curr_doc`, `prev_doc`, `change_diff` dispatch |
|
|
187
|
+
| `lib/.../model/diffable/hash_diff_nesting.rb` | `change_data`, `patch_*`, `dig_*` helpers |
|
|
188
|
+
| `lib/.../model/as_input.rb` | `as_input` instance + class methods, `patchVer` injection |
|
|
189
|
+
| `lib/.../graphql/logic/input.rb` | `Logic::Input.from_model` base |
|
|
190
|
+
| `lib/.../input/contractor_entity/update.rb` | `from_model` override with IdDiff reshaping |
|
|
191
|
+
| `spec/.../model/diff_service_spec.rb` | DiffService specs (WIP — partial failures) |
|
|
192
|
+
| `spec/.../model/as_input_spec.rb` | AsInput + from_model specs (WIP) |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# EcoPortal Architecture — Agent Reference Manual
|
|
2
|
+
|
|
3
|
+
**Purpose:** Enable an AI agent to read this folder, then immediately navigate the
|
|
4
|
+
ecoPortal source code, customer documentation, and integration gems with full context.
|
|
5
|
+
Covers: platform purpose, data model, naming conventions, API layers, workflows,
|
|
6
|
+
search, integrations, and documentation gaps.
|
|
7
|
+
|
|
8
|
+
**Session:** Accumulated 2026-06-07 from developer context, production Insomnia queries,
|
|
9
|
+
corpus documents, eco-helpers source audit, and GraphQL schema introspection.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Document Index
|
|
14
|
+
|
|
15
|
+
| File | Content |
|
|
16
|
+
|------|---------|
|
|
17
|
+
| `00_overview_and_index.md` | This file — platform summary + index |
|
|
18
|
+
| `01_terminology_dictionary.md` | Every name things are called — Rosetta Stone for disambiguating |
|
|
19
|
+
| `02_data_model.md` | Page hierarchy, types, MongoDB structure, genome, field ID |
|
|
20
|
+
| `03_api_layers.md` | APIv2 vs GraphQL — differences, retirement timeline, access |
|
|
21
|
+
| `04_graphql_queries_mutations.md` | All known queries/mutations, fragments, CommonPageUnion |
|
|
22
|
+
| `05_page_workflows.md` | Create/Update/Archive/Submit — 2-step sequences, stage logic |
|
|
23
|
+
| `06_search_and_filters.md` | Org search vs register search, SearchConf, filter operations |
|
|
24
|
+
| `07_data_fields.md` | All 20+ field types, DataFieldInput, update/addition patterns |
|
|
25
|
+
| `08_stages_sections.md` | Stage lifecycle, permissions, sections, HasSectionsInterface |
|
|
26
|
+
| `09_people_contractors_locations.md` | PersonMember, ContractorEntity, IdDiff, location tags |
|
|
27
|
+
| `10_forces_workflow_builder.md` | Forces (AngularJS legacy), Workflow Builder (in progress) |
|
|
28
|
+
| `11_integration_gems.md` | eco-helpers, ecoportal-api-v2, ecoportal-api-graphql chain |
|
|
29
|
+
| `12_ai_documentation_sources_gaps.md` | All knowledge sources + gaps + improvements for AI agents |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Platform Summary
|
|
34
|
+
|
|
35
|
+
EcoPortal is a SaaS **governance, risk and compliance (GRC)** platform built for
|
|
36
|
+
enterprise customers. It enables organisations to manage:
|
|
37
|
+
- Risk registers (pages = records in a workflow-driven register)
|
|
38
|
+
- Incident reporting
|
|
39
|
+
- Contractor management
|
|
40
|
+
- Action tracking
|
|
41
|
+
- Location/reporting structure management
|
|
42
|
+
- Visitor management (check-in)
|
|
43
|
+
- Notifications, dashboards, analytics
|
|
44
|
+
|
|
45
|
+
**Technology stack:**
|
|
46
|
+
- Backend: Ruby on Rails, MongoDB (NoSQL document store)
|
|
47
|
+
- Frontend: AngularJS (legacy, being replaced) → React (current, in progress)
|
|
48
|
+
- APIs: APIv2 REST (being retired), GraphQL (current standard)
|
|
49
|
+
- Search/analytics: Elasticsearch
|
|
50
|
+
- Deployment: Docker, cloud-hosted
|
|
51
|
+
|
|
52
|
+
**Key architectural principle:** MongoDB's flexible document model means page records
|
|
53
|
+
("instances") are cloned from templates. Field structure is defined in the template;
|
|
54
|
+
field data is stored per-instance. Field IDs are assigned by MongoDB on creation and
|
|
55
|
+
are instance-specific (not predictable from the template alone).
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# EcoPortal Terminology Dictionary
|
|
2
|
+
|
|
3
|
+
An AI agent MUST read this before working with ecoPortal code or customer requests.
|
|
4
|
+
The same concept has different names depending on who is talking and which layer of
|
|
5
|
+
the system is being discussed. Misunderstanding these leads to wrong assumptions.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Entity Names
|
|
10
|
+
|
|
11
|
+
### Pages / Records / Ooze / Forms
|
|
12
|
+
|
|
13
|
+
| Context | Name used | Notes |
|
|
14
|
+
|---------|-----------|-------|
|
|
15
|
+
| Customer-facing (product UI) | **Page**, **Record** | "Page" is the UI term; "Record" used in registers |
|
|
16
|
+
| eco-helpers scripts (internal) | **Ooze** | Internal dev shorthand, from legacy gem name |
|
|
17
|
+
| eco-helpers class names | `OozeBase`, `RegisterUpdate` | Script use case base classes |
|
|
18
|
+
| Confluence docs | **Page** | |
|
|
19
|
+
| APIv2 gem | `Ecoportal::API::V2::Page` | |
|
|
20
|
+
| GraphQL schema | `BasicPage`, `PhasedPage`, `PageUnion` | Two concrete types |
|
|
21
|
+
| GraphQL queries | `page(id:)`, `pages(...)` | |
|
|
22
|
+
| ecoportal-api-graphql gem | `Model::Page::Basic`, `Model::Page::Phased` | |
|
|
23
|
+
|
|
24
|
+
**Disambiguation rule:** When a customer says "record" or "page", they mean a page
|
|
25
|
+
instance — NOT a template. Templates are a separate concept (see below).
|
|
26
|
+
|
|
27
|
+
### Stages / Workflow Steps / Flow Nodes
|
|
28
|
+
|
|
29
|
+
| Context | Name used | Notes |
|
|
30
|
+
|---------|-----------|-------|
|
|
31
|
+
| Customer-facing (product UI) | **Stage** | |
|
|
32
|
+
| Backend Enzyme namespace (old) | **flow_nodes** | Old Rails model name |
|
|
33
|
+
| Backend NewEp namespace (current) | **stages** | |
|
|
34
|
+
| APIv2 | **stages** | |
|
|
35
|
+
| GraphQL schema | `Stage`, `StageIndex`, `StageStateEnum` | |
|
|
36
|
+
| eco-helpers | **stages** | `page.stages[name]`, `page.stages.ordered` |
|
|
37
|
+
|
|
38
|
+
### Sections / Content Containers / Flow Node Sub-elements
|
|
39
|
+
|
|
40
|
+
| Context | Name used | Notes |
|
|
41
|
+
|---------|-----------|-------|
|
|
42
|
+
| Customer-facing (UI) | **Section** | Within a stage |
|
|
43
|
+
| Backend Enzyme namespace (old) | **flow_nodes** sub-elements | No clean separation |
|
|
44
|
+
| Backend NewEp namespace (current) | **sections** | `HasSectionsInterface` |
|
|
45
|
+
| APIv2 | **sections** | `page.sections` |
|
|
46
|
+
| GraphQL schema | `SectionUnion`, `ContentSection`, `SplitSection` | Two section types |
|
|
47
|
+
| eco-helpers | **sections** | `entry.sections.get_by_type(:content_section)` |
|
|
48
|
+
|
|
49
|
+
**Important:** BasicPage (no stages) has sections DIRECTLY. PhasedPage has sections
|
|
50
|
+
WITHIN each stage. This is why `page.sections` on a PhasedPage requires knowing which
|
|
51
|
+
stage you're in.
|
|
52
|
+
|
|
53
|
+
### Data Fields / Components / Membranes / Field Instances
|
|
54
|
+
|
|
55
|
+
| Context | Name used | Notes |
|
|
56
|
+
|---------|-----------|-------|
|
|
57
|
+
| Customer-facing (UI) | **Field** | Form field within a section |
|
|
58
|
+
| Backend Enzyme namespace (old) | **Membrane**, `Enzyme::Membrane` | Old model name |
|
|
59
|
+
| Backend NewEp namespace (current) | **DataField**, `NewEp::DataField` | |
|
|
60
|
+
| APIv2 | **component** | `page.components`, `component.type` |
|
|
61
|
+
| GraphQL schema | `DataFieldUnion`, `DataFieldsInterface` | 20+ concrete types |
|
|
62
|
+
| eco-helpers | **components** | `entry.components.get_by_type(:plain_text)` |
|
|
63
|
+
| ecoportal-api-graphql gem | `DataField::PlainText`, `DataField::Select`, etc. | |
|
|
64
|
+
|
|
65
|
+
**CRITICAL:** A "field" in the UI is an INSTANCE of a field definition. The template
|
|
66
|
+
defines the field structure (label, type, options). The page instance has the actual
|
|
67
|
+
field with server-assigned MongoDB ID and stored value. The instance field ID CANNOT be
|
|
68
|
+
predicted from the template alone — must be fetched after creation via `buildFromTemplate`.
|
|
69
|
+
|
|
70
|
+
### Registers / Registries / Page Collections
|
|
71
|
+
|
|
72
|
+
| Context | Name used | Notes |
|
|
73
|
+
|---------|-----------|-------|
|
|
74
|
+
| Customer-facing (UI) | **Register** | A themed collection of pages with a shared template |
|
|
75
|
+
| GraphQL schema | `Register`, `register(id:)` | |
|
|
76
|
+
| eco-helpers | **register** | `registers.search(register_id, ...)` |
|
|
77
|
+
|
|
78
|
+
### Templates
|
|
79
|
+
|
|
80
|
+
| Context | Name used | Notes |
|
|
81
|
+
|---------|-----------|-------|
|
|
82
|
+
| Customer-facing (UI) | **Template** | A "page type" defining field structure |
|
|
83
|
+
| GraphQL schema | `PageUnion` (templates are also pages), `Template` type | |
|
|
84
|
+
| eco-helpers | **template** | `pages.get_new(template_id)` |
|
|
85
|
+
|
|
86
|
+
**IMPORTANT:** Engineering does NOT want templates updated via GraphQL currently.
|
|
87
|
+
Template editing uses the old AngularJS front-end. Template mutations exist in the
|
|
88
|
+
schema but are not production-approved for scripting.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Field-Level Terminology
|
|
93
|
+
|
|
94
|
+
### ref / Field ID / ES Key / System ID (future)
|
|
95
|
+
|
|
96
|
+
The `ref` property on a data field (`Enzyme::Membrane` / `NewEp::DataField`) is the
|
|
97
|
+
**Elasticsearch index key** for that field. It determines how analytics, charts, filters,
|
|
98
|
+
and cross-register configurations identify the field.
|
|
99
|
+
|
|
100
|
+
**Composition:** `type_shorthand.hash_of_label`
|
|
101
|
+
- `type_shorthand`: e.g. `plain_text`, `date`, `select_str`, `select_num`, `gauge`,
|
|
102
|
+
`rich_text`, `actions_list`, `files`
|
|
103
|
+
- Hash of the label name (truncated when label > 3 characters)
|
|
104
|
+
- Formula: `Ecoportal::API::Common::Content::StringDigest` in `ecoportal-api-v2`
|
|
105
|
+
|
|
106
|
+
**Future:** The team is planning to formalise this as a user-visible "Field ID" (as
|
|
107
|
+
opposed to the internal MongoDB `id`). Currently it is only used internally.
|
|
108
|
+
|
|
109
|
+
**In search/sorters:** The `key` field in sorters (`{ key: 'updated_at', direction: 'asc' }`)
|
|
110
|
+
references this ref value, not the MongoDB field `id`.
|
|
111
|
+
|
|
112
|
+
### patchVer / Optimistic Concurrency
|
|
113
|
+
|
|
114
|
+
`patchVer: Int!` on every page. The server uses this for optimistic concurrency control.
|
|
115
|
+
**Any update mutation MUST include the current `patchVer` value fetched from the page.**
|
|
116
|
+
If two scripts try to update the same page concurrently, the second will get a stale-patchVer
|
|
117
|
+
error. Scripts must re-fetch the page to get the current `patchVer` before retrying.
|
|
118
|
+
|
|
119
|
+
### Genome Signature (mostly unused)
|
|
120
|
+
|
|
121
|
+
The genome signature was designed to pair fields ACROSS pages and their source templates.
|
|
122
|
+
Purpose: know that "this field in page X is the same field as this field in template Y".
|
|
123
|
+
**Current state:** Mostly unused. The Migrator Rails service that used it was abandoned.
|
|
124
|
+
Not indexed in ES. Many fields have broken genomes due to bulk tech scripts that added
|
|
125
|
+
fields without copying from the template. Do NOT design scripting around genome.
|
|
126
|
+
|
|
127
|
+
### Forces (legacy front-end snippets)
|
|
128
|
+
|
|
129
|
+
Forces are AngularJS embedded code snippets that attach to specific stages via bindings.
|
|
130
|
+
They implement conditional field behaviour (show/hide sections, computed risk matrices).
|
|
131
|
+
**Being replaced** by the Workflow Builder. Not accessible or useful via GraphQL.
|
|
132
|
+
Low scripting priority.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## State / Status Names
|
|
137
|
+
|
|
138
|
+
### Page State (`StateEnum`)
|
|
139
|
+
|
|
140
|
+
| Value | Meaning |
|
|
141
|
+
|-------|---------|
|
|
142
|
+
| `active` | Normal working state |
|
|
143
|
+
| `archived` | Soft-deleted/hidden — can be unarchived |
|
|
144
|
+
| `draft` | Not yet published (also `draft: Boolean` field) |
|
|
145
|
+
| `inprogress` | In a workflow stage (used in some contexts) |
|
|
146
|
+
|
|
147
|
+
### Stage State (`StageStateEnum`)
|
|
148
|
+
|
|
149
|
+
| Value | Meaning |
|
|
150
|
+
|-------|---------|
|
|
151
|
+
| `pending` | Not yet started |
|
|
152
|
+
| `inprogress` | Currently active |
|
|
153
|
+
| `complete` | Completed (all tasks done) |
|
|
154
|
+
|
|
155
|
+
### Task State
|
|
156
|
+
|
|
157
|
+
Tasks (`TaskInterface`, `CompletePage`, `ReviewPage`) have their own states related to
|
|
158
|
+
completion. Stage close-out is triggered by task completion.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## API Layer Names
|
|
163
|
+
|
|
164
|
+
| Layer | Name | Status |
|
|
165
|
+
|-------|------|--------|
|
|
166
|
+
| Legacy REST | **APIv2**, `ecoportal-api-v2` gem | Being retired (3 weeks from 2026-06-07) |
|
|
167
|
+
| Current GraphQL | **GraphQL API**, `ecoportal-api-graphql` gem | Current standard |
|
|
168
|
+
| Internal REST (auth) | **APIv1** (OAuth) | Still in use for authentication only |
|
|
169
|
+
| Future | **APIv3** (page render / custom exports) | In development, not production |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Backend Namespace Names
|
|
174
|
+
|
|
175
|
+
| Namespace | Description | Location |
|
|
176
|
+
|-----------|-------------|----------|
|
|
177
|
+
| `Enzyme` | Old Rails model namespace (AngularJS era) | `C:\docker\ecoPortal_master\` |
|
|
178
|
+
| `NewEp` | Current Rails model namespace (React era migration) | Same repo |
|
|
179
|
+
| `Ecoportal::API::V2` | REST API gem | `C:\ruby_scripts\git\ecoportal-api-v2` |
|
|
180
|
+
| `Ecoportal::API::GraphQL` | GraphQL gem | THIS REPO |
|
|
181
|
+
| `Eco` (eco-helpers) | Integration scripts helper gem | `C:\ruby_scripts\git\eco-helpers` |
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# EcoPortal Data Model
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Top-Level Hierarchy
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Organisation
|
|
9
|
+
├── Registers (themed page collections)
|
|
10
|
+
│ ├── Templates (page type definitions)
|
|
11
|
+
│ │ ├── Stages (workflow steps — PhasedPage templates)
|
|
12
|
+
│ │ │ └── Sections
|
|
13
|
+
│ │ │ └── DataField definitions (with label, type, options)
|
|
14
|
+
│ │ └── Sections (for BasicPage templates)
|
|
15
|
+
│ │ └── DataField definitions
|
|
16
|
+
│ └── Pages / Instances (created from templates)
|
|
17
|
+
│ ├── [BasicPage] Sections → DataFields (actual data, server IDs)
|
|
18
|
+
│ └── [PhasedPage] Stages → Sections → DataFields (actual data, server IDs)
|
|
19
|
+
├── People (PersonMember)
|
|
20
|
+
├── Contractor Entities
|
|
21
|
+
├── Location Structure (reporting structure / RS)
|
|
22
|
+
│ └── Location Nodes (hierarchical tree)
|
|
23
|
+
├── Actions (standalone or linked to pages)
|
|
24
|
+
└── User Groups / Permissions
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## `externalId` — per-org unique key for integrations (VERIFIED, Oscar 2026-07-02)
|
|
30
|
+
|
|
31
|
+
`externalId` exists on models (pages, and other objects) as the **integration pairing key**:
|
|
32
|
+
|
|
33
|
+
- **Uniqueness is per-organization, per-model:** an `externalId` must be unique among objects of
|
|
34
|
+
the *same model* within one ecoPortal organization. It is the mechanism the platform uses to
|
|
35
|
+
**prevent double-ups** and to give a customer integration a stable way to **pair its own record
|
|
36
|
+
ids with ecoPortal's objects** (set `externalId` = the customer's key; look up / upsert by it).
|
|
37
|
+
- **Implication for integrations:** when you control the source data, prefer keying create/upsert
|
|
38
|
+
on `externalId` (guaranteed unique, server-enforced) over searching a data-field value. A
|
|
39
|
+
field-value search can miss (wrong key, analyzer semantics — see the cans-upsert dup incident in
|
|
40
|
+
`search_filters.md`) and silently create duplicates; an `externalId` collision is rejected.
|
|
41
|
+
- **Archiving contingency:** clear the `externalId` only when archiving is a *cleanup of a failed /
|
|
42
|
+
duplicate creation* (so the freed id can be reused); a genuine historical archive should **keep**
|
|
43
|
+
its `externalId`. The backend `archivePage` mutation takes only `id` (no clear-externalId arg), so
|
|
44
|
+
this is a **client composition**: `updatePage(page:{externalId:null})` + `archivePage`, in one
|
|
45
|
+
document (the maintainer's Insomnia sample gates the updatePage with `@include(if:$blank_external_id)`).
|
|
46
|
+
WIRED as **`Builder::Page#archive(clear_external_id: false)`** (default keeps it) — implemented as
|
|
47
|
+
two serial mutations (blank-then-archive) reusing the existing update/archive mutations.
|
|
48
|
+
- **Note:** the cans-upsert case does NOT use `externalId` (it keys on the `Identifier:` data
|
|
49
|
+
field) — which is exactly why a wrong search key produced duplicates. An `externalId`-based
|
|
50
|
+
upsert would have been collision-safe.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Page Types (PageUnion)
|
|
55
|
+
|
|
56
|
+
The GraphQL `PageUnion = BasicPage | PhasedPage`. Both implement `BasePageInterface`.
|
|
57
|
+
|
|
58
|
+
### BasicPage
|
|
59
|
+
|
|
60
|
+
- Has sections DIRECTLY (no stages)
|
|
61
|
+
- Used for: simple forms, non-workflow records
|
|
62
|
+
- `implements: BasePageInterface, HasSectionsInterface`
|
|
63
|
+
- `sections: [SectionUnion!]!` — direct access
|
|
64
|
+
|
|
65
|
+
### PhasedPage
|
|
66
|
+
|
|
67
|
+
- Has stages, each stage has sections
|
|
68
|
+
- Used for: workflow-driven records (risk assessments, incident reports, inspections)
|
|
69
|
+
- `implements: BasePageInterface`
|
|
70
|
+
- `stages: [Stage!]!` — access via StageCollection
|
|
71
|
+
- `stagesIndex: [StageIndex!]!` — lightweight stage summary
|
|
72
|
+
- `currentStage: Stage!` — the currently active stage
|
|
73
|
+
|
|
74
|
+
### Identifying the Type
|
|
75
|
+
|
|
76
|
+
Always include `__typename` in page queries. The server returns `'BasicPage'` or
|
|
77
|
+
`'PhasedPage'`. In the ecoportal-api-graphql gem, `Model::PageUnion.new(doc)` dispatches
|
|
78
|
+
on `__typename` to create the right concrete class.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## MongoDB Document Structure
|
|
83
|
+
|
|
84
|
+
Pages are stored as MongoDB documents. The document structure is flexible — field
|
|
85
|
+
instances are embedded within the stage/section hierarchy.
|
|
86
|
+
|
|
87
|
+
**Key implications:**
|
|
88
|
+
1. **Field IDs are MongoDB ObjectIds** assigned at creation. They are DIFFERENT from
|
|
89
|
+
the template's field IDs. An instance field ID cannot be predicted — must be fetched.
|
|
90
|
+
2. **patchVer** is an integer version counter incremented on every save. Required for
|
|
91
|
+
all update mutations.
|
|
92
|
+
3. **Partial updates** — the GraphQL `updatePage` mutation only changes the fields you
|
|
93
|
+
specify. The rest remain unchanged. This is different from APIv2 which sometimes sent
|
|
94
|
+
the full document.
|
|
95
|
+
4. **Stages are embedded** within the page document. Fetching a page gets all stages.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Template vs Instance
|
|
100
|
+
|
|
101
|
+
| Aspect | Template | Instance (Page) |
|
|
102
|
+
|--------|----------|-----------------|
|
|
103
|
+
| Created by | Admins via UI | Scripts or end users |
|
|
104
|
+
| Field IDs | Template-specific ObjectIds | NEW ObjectIds assigned on creation |
|
|
105
|
+
| patchVer | Yes | Yes |
|
|
106
|
+
| Stage structure | Defines stage names/order | Inherited at creation, runtime state |
|
|
107
|
+
| DataField content | Default values only | Actual user-entered values |
|
|
108
|
+
| GraphQL access | `templates(...)` query | `pages(...)` or `page(id:)` |
|
|
109
|
+
|
|
110
|
+
**CRITICAL for scripting:** After `buildFromTemplate`, the response contains the
|
|
111
|
+
INSTANCE field IDs. These are the IDs to use for `dataFields.updates` in
|
|
112
|
+
`createFromTemplate`. The template field IDs are irrelevant for instance operations.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Stage Model (PhasedPage)
|
|
117
|
+
|
|
118
|
+
Each stage in a PhasedPage:
|
|
119
|
+
- Has a unique `id` (MongoDB ObjectId)
|
|
120
|
+
- Has `name` (the stage name configured in the template)
|
|
121
|
+
- Has `ordering` (display order, integer)
|
|
122
|
+
- Has `state` (`StageStateEnum`: pending/inprogress/complete)
|
|
123
|
+
- Has `active: Boolean` (whether stage is currently active for input)
|
|
124
|
+
- Has `started: Boolean` (whether any input has been recorded)
|
|
125
|
+
- Has `sections: [SectionUnion!]!` — the stage's content sections
|
|
126
|
+
- Has task references: `latestCompletePageTask`, `latestReviewPageTask`, `activeTask`
|
|
127
|
+
|
|
128
|
+
**Stage permissions:**
|
|
129
|
+
- Each stage can have user group restrictions
|
|
130
|
+
- People fields can grant direct stage access based on their values
|
|
131
|
+
- Configuration enum on people fields determines scope (stage-specific vs page-wide)
|
|
132
|
+
- GraphQL silently omits stages the user cannot access (no access errors)
|
|
133
|
+
|
|
134
|
+
**Stage transitions:**
|
|
135
|
+
- Triggered by task completion (complete task = stage done → next stage starts)
|
|
136
|
+
- Two task types: `CompletePage` (fill-in) and `ReviewPage` (sign-off)
|
|
137
|
+
- Submitting a stage via GraphQL `updatePage(stageId:, submit: true)` triggers the
|
|
138
|
+
server-side task assignment rules (who gets the next task per workflow config)
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Section Model
|
|
143
|
+
|
|
144
|
+
Two types (`SectionUnion = ContentSection | SplitSection`):
|
|
145
|
+
|
|
146
|
+
### ContentSection
|
|
147
|
+
- `heading: String` — section heading
|
|
148
|
+
- `dataFields: [DataFieldUnion!]!` — fields in this section
|
|
149
|
+
|
|
150
|
+
### SplitSection
|
|
151
|
+
- `heading: String`
|
|
152
|
+
- `leftDataFields: [DataFieldUnion!]!`
|
|
153
|
+
- `rightDataFields: [DataFieldUnion!]!`
|
|
154
|
+
|
|
155
|
+
Both implement `HasSectionsInterface`. When a section's fields need to be accessed
|
|
156
|
+
together, merge `dataFields + leftDataFields + rightDataFields`.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Genome Signature (Field Pairing — Mostly Unused)
|
|
161
|
+
|
|
162
|
+
Each field definition (in template and in instances) carries a **genome signature** —
|
|
163
|
+
a hash intended to identify that a field is "the same" across template and instances.
|
|
164
|
+
|
|
165
|
+
**Original design:** Enable the Migrator service to propagate template changes to
|
|
166
|
+
existing pages by pairing fields via genome.
|
|
167
|
+
|
|
168
|
+
**Current reality:**
|
|
169
|
+
- Largely abandoned — the Migrator service is inactive
|
|
170
|
+
- Many instances have broken genomes (bulk tech scripts that added fields
|
|
171
|
+
without copying from the template gave them random creation-time genomes)
|
|
172
|
+
- Not indexed in Elasticsearch
|
|
173
|
+
- Unknown whether `NewEp` fully ported the genome from `Enzyme`
|
|
174
|
+
- Do NOT design scripting logic around genome — it cannot be relied upon
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Location Structure (Reporting Structure)
|
|
179
|
+
|
|
180
|
+
The organisation has a hierarchical location tree. Locations are used for:
|
|
181
|
+
- Tagging pages (assigning pages to locations/departments)
|
|
182
|
+
- Filtering in searches (`location_ids` filter)
|
|
183
|
+
- Permissions scoping
|
|
184
|
+
- Analytics/dashboard scoping
|
|
185
|
+
|
|
186
|
+
The location tree is managed via dedicated mutations (`draft` system for structural edits).
|
|
187
|
+
Location nodes have: `id`, `name`, `weight`, `archived`, `archivedToken`, `classifications`,
|
|
188
|
+
`ancestors`, `parent`, `children`.
|
|
189
|
+
|
|
190
|
+
**`baseTags` vs `otherTags`:**
|
|
191
|
+
- `baseTags` — location tags inherited from the base template configuration
|
|
192
|
+
- `otherTags` — additional free-text tags attached to a page instance
|