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.
Files changed (499) hide show
  1. checksums.yaml +4 -4
  2. data/.ai-assistance/bridge/CLAUDE.md +338 -0
  3. data/.ai-assistance/bridge/archive/.gitkeep +0 -0
  4. data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +29 -0
  5. data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +18 -0
  6. data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +42 -0
  7. data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +115 -0
  8. data/.ai-assistance/bridge/context/gemini-review-prompt.txt +48 -0
  9. data/.ai-assistance/bridge/context/gemini-review-response.md +104 -0
  10. data/.ai-assistance/bridge/context/project.md +42 -0
  11. data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
  12. data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
  13. data/.ai-assistance/bridge/outbox/request-for-standards-discovery.md +48 -0
  14. data/.ai-assistance/bridge/queue/.gitkeep +1 -0
  15. data/.ai-assistance/capabilities/CLAUDE.md +27 -0
  16. data/.ai-assistance/capabilities/assumptions-log.md +80 -0
  17. data/.ai-assistance/capabilities/code.md +47 -0
  18. data/.ai-assistance/capabilities/connectors.md +37 -0
  19. data/.ai-assistance/capabilities/cowork.md +55 -0
  20. data/.ai-assistance/code/OVERVIEW.md +155 -0
  21. data/.ai-assistance/code/data_fields.md +242 -0
  22. data/.ai-assistance/code/dependencies.md +151 -0
  23. data/.ai-assistance/code/diff_as_input.md +234 -0
  24. data/.ai-assistance/code/diff_service_deep_dive.md +192 -0
  25. data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +55 -0
  26. data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +181 -0
  27. data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +192 -0
  28. data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +147 -0
  29. data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +277 -0
  30. data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +200 -0
  31. data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +228 -0
  32. data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +197 -0
  33. data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +243 -0
  34. data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +196 -0
  35. data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +132 -0
  36. data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +187 -0
  37. data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +236 -0
  38. data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +183 -0
  39. data/.ai-assistance/code/ecoportal_schema_reference.md +240 -0
  40. data/.ai-assistance/code/graphql_domain_knowledge.md +230 -0
  41. data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +71 -0
  42. data/.ai-assistance/code/refactoring/opportunities.md +251 -0
  43. data/.ai-assistance/code/schema_analysis.md +321 -0
  44. data/.ai-assistance/code/search_filters.md +868 -0
  45. data/.ai-assistance/code/spec_coverage.md +73 -0
  46. data/.ai-assistance/code/workflow-command-guide.md +438 -0
  47. data/.ai-assistance/code/workflow-space.md +353 -0
  48. data/.ai-assistance/conventions/CLAUDE.md +30 -0
  49. data/.ai-assistance/conventions/code-working-tree-protocol.md +199 -0
  50. data/.ai-assistance/conventions/gitignore-rules.md +42 -0
  51. data/.ai-assistance/conventions/permission-guidance.md +120 -0
  52. data/.ai-assistance/integrations/README.md +70 -0
  53. data/.ai-assistance/integrations/gitkraken-mcp.md +107 -0
  54. data/.ai-assistance/integrations/gitlab-mcp.md +123 -0
  55. data/.ai-assistance/integrations/local-git.md +60 -0
  56. data/.ai-assistance/local_paths.example.md +17 -0
  57. data/.ai-assistance/projects/TODO.md +97 -0
  58. data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +168 -0
  59. data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +60 -0
  60. data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +267 -0
  61. data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +53 -0
  62. data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +102 -0
  63. data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +139 -0
  64. data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +93 -0
  65. data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +244 -0
  66. data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +266 -0
  67. data/.ai-assistance/projects/eco-helpers-compat/TODO.md +86 -0
  68. data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +101 -0
  69. data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +177 -0
  70. data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +161 -0
  71. data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +125 -0
  72. data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +126 -0
  73. data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +256 -0
  74. data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +122 -0
  75. data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +148 -0
  76. data/.ai-assistance/projects/page-model/DECISIONS.md +245 -0
  77. data/.ai-assistance/projects/page-model/TODO.md +190 -0
  78. data/.ai-assistance/projects/search-filter-builder/INTENT.md +107 -0
  79. data/.ai-assistance/projects/search-filter-builder/TODO.md +131 -0
  80. data/.ai-assistance/projects/template-maintenance/DESIGN.md +134 -0
  81. data/.ai-assistance/projects/workflow-space/TODO.md +213 -0
  82. data/.ai-assistance/reinstall-claude-desktop-windows.md +136 -0
  83. data/.ai-assistance/scripts/CLAUDE.md +150 -0
  84. data/.ai-assistance/scripts/bridge-init.sh +86 -0
  85. data/.ai-assistance/scripts/bridge-status.sh +44 -0
  86. data/.ai-assistance/scripts/capabilities-check.ts +104 -0
  87. data/.ai-assistance/scripts/check-outbox.sh +43 -0
  88. data/.ai-assistance/scripts/dep_graph.rb +91 -0
  89. data/.ai-assistance/scripts/lock-acquire.sh +103 -0
  90. data/.ai-assistance/scripts/lock-multi.sh +124 -0
  91. data/.ai-assistance/scripts/lock-queue.sh +94 -0
  92. data/.ai-assistance/scripts/setup-mcps.test.ts +188 -0
  93. data/.ai-assistance/scripts/setup-mcps.ts +234 -0
  94. data/.ai-assistance/scripts/task-complete.ts +74 -0
  95. data/.ai-assistance/scripts/task-create.ts +75 -0
  96. data/.ai-assistance/scripts/task-read.ts +125 -0
  97. data/.ai-assistance/scripts/token-logger.js +220 -0
  98. data/.ai-assistance/scripts/token-report.ts +158 -0
  99. data/.ai-assistance/scripts/token-session-start.js +66 -0
  100. data/.ai-assistance/skills/ai-instructions/SKILL.md +48 -0
  101. data/.ai-assistance/skills/code-specs/SKILL.md +69 -0
  102. data/.ai-assistance/skills/corporate-policies/SKILL.md +201 -0
  103. data/.ai-assistance/skills/dep-graph/SKILL.md +139 -0
  104. data/.ai-assistance/skills/ep-ai-manager/SKILL.md +417 -0
  105. data/.ai-assistance/skills/gemini-assist/SKILL.md +63 -0
  106. data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +205 -0
  107. data/.ai-assistance/skills/gemini-assist/gemini_ask.py +1 -0
  108. data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +240 -0
  109. data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +25 -0
  110. data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +261 -0
  111. data/.ai-assistance/skills/project-cycle/SKILL.md +177 -0
  112. data/.ai-assistance/skills/refactor/SKILL.md +62 -0
  113. data/.ai-assistance/skills/rubocop/SKILL.md +93 -0
  114. data/.ai-assistance/skills/ruby-scripting/SKILL.md +215 -0
  115. data/.ai-assistance/skills/spec-generation/SKILL.md +72 -0
  116. data/.ai-assistance/standards-version.json +21 -0
  117. data/.ai-assistance/token-budget.json +32 -0
  118. data/.ai-assistance/version.json +39 -0
  119. data/.claude/settings.json +146 -0
  120. data/.env.example +18 -0
  121. data/.gitattributes +15 -0
  122. data/.gitignore +13 -0
  123. data/.rubocop.yml +121 -97
  124. data/CHANGELOG.md +673 -477
  125. data/CLAUDE.md +232 -0
  126. data/Gemfile +30 -6
  127. data/Rakefile +90 -38
  128. data/docs/worklog.md +574 -0
  129. data/ecoportal-api-graphql.gemspec +40 -40
  130. data/lib/ecoportal/api/common/graphql/CLAUDE.md +36 -0
  131. data/lib/ecoportal/api/common/graphql/client.rb +1 -1
  132. data/lib/ecoportal/api/common/graphql/http_client.rb +35 -3
  133. data/lib/ecoportal/api/common/graphql/model/CLAUDE.md +28 -0
  134. data/lib/ecoportal/api/common/graphql/model/as_input.rb +8 -5
  135. data/lib/ecoportal/api/common/graphql/model/diffable/classic_diff_service.rb +3 -1
  136. data/lib/ecoportal/api/common/graphql/model/diffable/diff_service.rb +31 -23
  137. data/lib/ecoportal/api/common/graphql/model/diffable/hash_diff_nesting.rb +54 -1
  138. data/lib/ecoportal/api/graphql/CLAUDE.md +37 -0
  139. data/lib/ecoportal/api/graphql/base/CLAUDE.md +50 -0
  140. data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -0
  141. data/lib/ecoportal/api/graphql/base/delta_result.rb +16 -0
  142. data/lib/ecoportal/api/graphql/base/force/binding.rb +19 -0
  143. data/lib/ecoportal/api/graphql/base/force/binding_collection.rb +62 -0
  144. data/lib/ecoportal/api/graphql/base/force/collection.rb +47 -0
  145. data/lib/ecoportal/api/graphql/base/force.rb +65 -0
  146. data/lib/ecoportal/api/graphql/base/kickstand/job.rb +17 -0
  147. data/lib/ecoportal/api/graphql/base/kickstand/workflow.rb +18 -0
  148. data/lib/ecoportal/api/graphql/base/kickstand.rb +13 -0
  149. data/lib/ecoportal/api/graphql/base/page/basic.rb +38 -0
  150. data/lib/ecoportal/api/graphql/base/page/data_field/actions_list.rb +9 -0
  151. data/lib/ecoportal/api/graphql/base/page/data_field/ai_summary.rb +18 -0
  152. data/lib/ecoportal/api/graphql/base/page/data_field/checklist.rb +29 -0
  153. data/lib/ecoportal/api/graphql/base/page/data_field/collection.rb +112 -0
  154. data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +28 -0
  155. data/lib/ecoportal/api/graphql/base/page/data_field/cross_reference.rb +54 -0
  156. data/lib/ecoportal/api/graphql/base/page/data_field/date_field.rb +23 -0
  157. data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +25 -0
  158. data/lib/ecoportal/api/graphql/base/page/data_field/gauge.rb +19 -0
  159. data/lib/ecoportal/api/graphql/base/page/data_field/geo.rb +24 -0
  160. data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +24 -0
  161. data/lib/ecoportal/api/graphql/base/page/data_field/law.rb +8 -0
  162. data/lib/ecoportal/api/graphql/base/page/data_field/mailbox.rb +9 -0
  163. data/lib/ecoportal/api/graphql/base/page/data_field/number.rb +20 -0
  164. data/lib/ecoportal/api/graphql/base/page/data_field/people.rb +35 -0
  165. data/lib/ecoportal/api/graphql/base/page/data_field/plain_text.rb +17 -0
  166. data/lib/ecoportal/api/graphql/base/page/data_field/rich_text.rb +26 -0
  167. data/lib/ecoportal/api/graphql/base/page/data_field/select.rb +41 -0
  168. data/lib/ecoportal/api/graphql/base/page/data_field/signature.rb +9 -0
  169. data/lib/ecoportal/api/graphql/base/page/data_field/smart_fill.rb +17 -0
  170. data/lib/ecoportal/api/graphql/base/page/data_field/table.rb +9 -0
  171. data/lib/ecoportal/api/graphql/base/page/data_field/tag_field.rb +21 -0
  172. data/lib/ecoportal/api/graphql/base/page/data_field.rb +137 -12
  173. data/lib/ecoportal/api/graphql/base/page/phased/stage.rb +68 -14
  174. data/lib/ecoportal/api/graphql/base/page/phased.rb +1 -0
  175. data/lib/ecoportal/api/graphql/base/page/section.rb +36 -0
  176. data/lib/ecoportal/api/graphql/base/page/section_collection.rb +79 -0
  177. data/lib/ecoportal/api/graphql/base/page.rb +2 -0
  178. data/lib/ecoportal/api/graphql/base/pages_workflow/action_type_selection.rb +17 -0
  179. data/lib/ecoportal/api/graphql/base/pages_workflow/callback_type.rb +28 -0
  180. data/lib/ecoportal/api/graphql/base/pages_workflow/command_change.rb +17 -0
  181. data/lib/ecoportal/api/graphql/base/pages_workflow/command_change_message.rb +15 -0
  182. data/lib/ecoportal/api/graphql/base/pages_workflow/command_es_change.rb +17 -0
  183. data/lib/ecoportal/api/graphql/base/pages_workflow/command_interface.rb +36 -0
  184. data/lib/ecoportal/api/graphql/base/pages_workflow/email_config.rb +18 -0
  185. data/lib/ecoportal/api/graphql/base/pages_workflow/escalation_level.rb +19 -0
  186. data/lib/ecoportal/api/graphql/base/pages_workflow/in_system_config.rb +16 -0
  187. data/lib/ecoportal/api/graphql/base/pages_workflow/mailbox_field_selection.rb +17 -0
  188. data/lib/ecoportal/api/graphql/base/pages_workflow/operation_interface.rb +39 -0
  189. data/lib/ecoportal/api/graphql/base/pages_workflow/operations/assign_to.rb +27 -0
  190. data/lib/ecoportal/api/graphql/base/pages_workflow/operations/create_page.rb +21 -0
  191. data/lib/ecoportal/api/graphql/base/pages_workflow/operations/send_notification.rb +30 -0
  192. data/lib/ecoportal/api/graphql/base/pages_workflow/people_field_selection.rb +17 -0
  193. data/lib/ecoportal/api/graphql/base/pages_workflow/recipient_config.rb +34 -0
  194. data/lib/ecoportal/api/graphql/base/pages_workflow/register_field.rb +17 -0
  195. data/lib/ecoportal/api/graphql/base/pages_workflow/task_config_selection.rb +17 -0
  196. data/lib/ecoportal/api/graphql/base/pages_workflow/time_delay_config.rb +16 -0
  197. data/lib/ecoportal/api/graphql/base/pages_workflow/trigger_interface.rb +26 -0
  198. data/lib/ecoportal/api/graphql/base/pages_workflow/triggers/conditional_logic.rb +17 -0
  199. data/lib/ecoportal/api/graphql/base/pages_workflow/user_selection.rb +16 -0
  200. data/lib/ecoportal/api/graphql/base/pages_workflow.rb +35 -0
  201. data/lib/ecoportal/api/graphql/base/preset_view.rb +17 -0
  202. data/lib/ecoportal/api/graphql/base/preview_page.rb +23 -0
  203. data/lib/ecoportal/api/graphql/base/register.rb +18 -0
  204. data/lib/ecoportal/api/graphql/base.rb +11 -0
  205. data/lib/ecoportal/api/graphql/builder/CLAUDE.md +65 -0
  206. data/lib/ecoportal/api/graphql/builder/kickstand.rb +73 -0
  207. data/lib/ecoportal/api/graphql/builder/page.rb +210 -41
  208. data/lib/ecoportal/api/graphql/builder/register/preset_view.rb +84 -0
  209. data/lib/ecoportal/api/graphql/builder/register.rb +27 -19
  210. data/lib/ecoportal/api/graphql/builder/template.rb +80 -0
  211. data/lib/ecoportal/api/graphql/builder.rb +2 -0
  212. data/lib/ecoportal/api/graphql/compat/filter_translator.rb +107 -0
  213. data/lib/ecoportal/api/graphql/compat/page_reference.rb +23 -0
  214. data/lib/ecoportal/api/graphql/compat/pages.rb +212 -0
  215. data/lib/ecoportal/api/graphql/compat/registers.rb +84 -0
  216. data/lib/ecoportal/api/graphql/compat/response.rb +35 -0
  217. data/lib/ecoportal/api/graphql/compat/search_results.rb +33 -0
  218. data/lib/ecoportal/api/graphql/compat/stage_collection.rb +70 -0
  219. data/lib/ecoportal/api/graphql/compat/stage_view.rb +76 -0
  220. data/lib/ecoportal/api/graphql/compat.rb +17 -0
  221. data/lib/ecoportal/api/graphql/concerns/data_field_access.rb +71 -0
  222. data/lib/ecoportal/api/graphql/concerns/deprecation.rb +20 -0
  223. data/lib/ecoportal/api/graphql/concerns/fragment_definitions.rb +21 -28
  224. data/lib/ecoportal/api/graphql/concerns/page_compat.rb +51 -0
  225. data/lib/ecoportal/api/graphql/concerns/snake_camel_access.rb +60 -0
  226. data/lib/ecoportal/api/graphql/concerns.rb +4 -0
  227. data/lib/ecoportal/api/graphql/connection/page.rb +11 -0
  228. data/lib/ecoportal/api/graphql/connection/pages_workflow_command.rb +13 -0
  229. data/lib/ecoportal/api/graphql/connection/preset_view.rb +11 -0
  230. data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -0
  231. data/lib/ecoportal/api/graphql/connection.rb +4 -0
  232. data/lib/ecoportal/api/graphql/file_upload/client.rb +181 -0
  233. data/lib/ecoportal/api/graphql/file_upload.rb +10 -0
  234. data/lib/ecoportal/api/graphql/fragment/action.rb +1 -1
  235. data/lib/ecoportal/api/graphql/fragment/action_category.rb +1 -1
  236. data/lib/ecoportal/api/graphql/fragment/contractor_entity.rb +1 -1
  237. data/lib/ecoportal/api/graphql/fragment/force.rb +30 -0
  238. data/lib/ecoportal/api/graphql/fragment/location_draft.rb +2 -2
  239. data/lib/ecoportal/api/graphql/fragment/location_node.rb +1 -1
  240. data/lib/ecoportal/api/graphql/fragment/locations_error.rb +1 -1
  241. data/lib/ecoportal/api/graphql/fragment/page.rb +85 -0
  242. data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +395 -0
  243. data/lib/ecoportal/api/graphql/fragment/pages.rb +15 -0
  244. data/lib/ecoportal/api/graphql/fragment/pages_workflow.rb +172 -0
  245. data/lib/ecoportal/api/graphql/fragment/pagination.rb +1 -1
  246. data/lib/ecoportal/api/graphql/fragment.rb +37 -27
  247. data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +25 -0
  248. data/lib/ecoportal/api/graphql/input/delta_input.rb +16 -0
  249. data/lib/ecoportal/api/graphql/input/page/archive.rb +14 -0
  250. data/lib/ecoportal/api/graphql/input/page/build_from_template.rb +13 -0
  251. data/lib/ecoportal/api/graphql/input/page/create_draft.rb +13 -0
  252. data/lib/ecoportal/api/graphql/input/page/create_from_template.rb +18 -0
  253. data/lib/ecoportal/api/graphql/input/page/delete_draft.rb +13 -0
  254. data/lib/ecoportal/api/graphql/input/page/publish_draft.rb +13 -0
  255. data/lib/ecoportal/api/graphql/input/page/review_task.rb +14 -0
  256. data/lib/ecoportal/api/graphql/input/page/unarchive.rb +14 -0
  257. data/lib/ecoportal/api/graphql/input/page/update.rb +140 -0
  258. data/lib/ecoportal/api/graphql/input/page.rb +26 -0
  259. data/lib/ecoportal/api/graphql/input/preset_view/create.rb +18 -0
  260. data/lib/ecoportal/api/graphql/input/preset_view/permission.rb +16 -0
  261. data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -0
  262. data/lib/ecoportal/api/graphql/input/preset_view.rb +14 -0
  263. data/lib/ecoportal/api/graphql/input/register/create.rb +18 -0
  264. data/lib/ecoportal/api/graphql/input/register/update.rb +15 -0
  265. data/lib/ecoportal/api/graphql/input/register.rb +13 -0
  266. data/lib/ecoportal/api/graphql/input/search_conf/ai_generator.rb +234 -0
  267. data/lib/ecoportal/api/graphql/input/search_conf.rb +367 -0
  268. data/lib/ecoportal/api/graphql/input/variable_binding.rb +20 -0
  269. data/lib/ecoportal/api/graphql/input/workflow_command/add_action_tag.rb +18 -0
  270. data/lib/ecoportal/api/graphql/input/workflow_command/add_binding.rb +18 -0
  271. data/lib/ecoportal/api/graphql/input/workflow_command/add_comment_tagging_user_group.rb +18 -0
  272. data/lib/ecoportal/api/graphql/input/workflow_command/add_default_direct_strategy_user.rb +18 -0
  273. data/lib/ecoportal/api/graphql/input/workflow_command/add_default_strategy.rb +18 -0
  274. data/lib/ecoportal/api/graphql/input/workflow_command/add_direct_strategy_user.rb +18 -0
  275. data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +18 -0
  276. data/lib/ecoportal/api/graphql/input/workflow_command/add_force.rb +18 -0
  277. data/lib/ecoportal/api/graphql/input/workflow_command/add_gauge_field_stop.rb +19 -0
  278. data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_field_config.rb +23 -0
  279. data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_helper.rb +18 -0
  280. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation.rb +18 -0
  281. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_direct_strategy_user.rb +18 -0
  282. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_strategy.rb +18 -0
  283. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_action_type.rb +18 -0
  284. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_filter.rb +18 -0
  285. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_people_field.rb +18 -0
  286. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_task_config.rb +18 -0
  287. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_user.rb +18 -0
  288. data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback.rb +23 -0
  289. data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback_action.rb +18 -0
  290. data/lib/ecoportal/api/graphql/input/workflow_command/add_section.rb +18 -0
  291. data/lib/ecoportal/api/graphql/input/workflow_command/add_select_field_option.rb +19 -0
  292. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -0
  293. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_section.rb +18 -0
  294. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_tag.rb +18 -0
  295. data/lib/ecoportal/api/graphql/input/workflow_command/add_strategy.rb +18 -0
  296. data/lib/ecoportal/api/graphql/input/workflow_command/add_task.rb +18 -0
  297. data/lib/ecoportal/api/graphql/input/workflow_command/add_task_assignment_user_group.rb +18 -0
  298. data/lib/ecoportal/api/graphql/input/workflow_command/add_workflow_callback.rb +18 -0
  299. data/lib/ecoportal/api/graphql/input/workflow_command/collapse_section.rb +18 -0
  300. data/lib/ecoportal/api/graphql/input/workflow_command/edit_binding.rb +18 -0
  301. data/lib/ecoportal/api/graphql/input/workflow_command/edit_creator_permissions.rb +18 -0
  302. data/lib/ecoportal/api/graphql/input/workflow_command/edit_default_strategy.rb +18 -0
  303. data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -0
  304. data/lib/ecoportal/api/graphql/input/workflow_command/edit_force.rb +18 -0
  305. data/lib/ecoportal/api/graphql/input/workflow_command/edit_gauge_field_stop.rb +19 -0
  306. data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_field_config.rb +19 -0
  307. data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_helper.rb +18 -0
  308. data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation.rb +18 -0
  309. data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation_strategy.rb +18 -0
  310. data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -0
  311. data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -0
  312. data/lib/ecoportal/api/graphql/input/workflow_command/edit_reminder.rb +18 -0
  313. data/lib/ecoportal/api/graphql/input/workflow_command/edit_required_sign_offs.rb +18 -0
  314. data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_comment_tagging.rb +18 -0
  315. data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_task_assignment.rb +18 -0
  316. data/lib/ecoportal/api/graphql/input/workflow_command/edit_scheduled_callback.rb +22 -0
  317. data/lib/ecoportal/api/graphql/input/workflow_command/edit_section_header.rb +18 -0
  318. data/lib/ecoportal/api/graphql/input/workflow_command/edit_select_field_option.rb +19 -0
  319. data/lib/ecoportal/api/graphql/input/workflow_command/edit_stage.rb +18 -0
  320. data/lib/ecoportal/api/graphql/input/workflow_command/edit_strategy.rb +18 -0
  321. data/lib/ecoportal/api/graphql/input/workflow_command/edit_task_due.rb +18 -0
  322. data/lib/ecoportal/api/graphql/input/workflow_command/edit_trigger.rb +18 -0
  323. data/lib/ecoportal/api/graphql/input/workflow_command/expand_section.rb +18 -0
  324. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/contractor_entities.rb +24 -0
  325. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -0
  326. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -0
  327. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/gauge.rb +20 -0
  328. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -0
  329. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/location_field.rb +24 -0
  330. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -0
  331. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -0
  332. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -0
  333. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -0
  334. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/signature.rb +20 -0
  335. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/table.rb +25 -0
  336. data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -0
  337. data/lib/ecoportal/api/graphql/input/workflow_command/move_stage.rb +18 -0
  338. data/lib/ecoportal/api/graphql/input/workflow_command/remove_action_tag.rb +18 -0
  339. data/lib/ecoportal/api/graphql/input/workflow_command/remove_binding.rb +18 -0
  340. data/lib/ecoportal/api/graphql/input/workflow_command/remove_callback.rb +18 -0
  341. data/lib/ecoportal/api/graphql/input/workflow_command/remove_comment_tagging_user_group.rb +18 -0
  342. data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_direct_strategy_user.rb +18 -0
  343. data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_strategy.rb +18 -0
  344. data/lib/ecoportal/api/graphql/input/workflow_command/remove_direct_strategy_user.rb +18 -0
  345. data/lib/ecoportal/api/graphql/input/workflow_command/remove_field.rb +18 -0
  346. data/lib/ecoportal/api/graphql/input/workflow_command/remove_force.rb +18 -0
  347. data/lib/ecoportal/api/graphql/input/workflow_command/remove_gauge_field_stop.rb +17 -0
  348. data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_field_config.rb +17 -0
  349. data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_helper.rb +18 -0
  350. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation.rb +18 -0
  351. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_direct_strategy_user.rb +18 -0
  352. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_strategy.rb +18 -0
  353. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_action_type.rb +18 -0
  354. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_filter.rb +18 -0
  355. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_people_field.rb +18 -0
  356. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_task_config.rb +18 -0
  357. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_user.rb +18 -0
  358. data/lib/ecoportal/api/graphql/input/workflow_command/remove_scheduled_callback.rb +18 -0
  359. data/lib/ecoportal/api/graphql/input/workflow_command/remove_section.rb +18 -0
  360. data/lib/ecoportal/api/graphql/input/workflow_command/remove_select_field_option.rb +17 -0
  361. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage.rb +18 -0
  362. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_section.rb +18 -0
  363. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_tag.rb +18 -0
  364. data/lib/ecoportal/api/graphql/input/workflow_command/remove_strategy.rb +18 -0
  365. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task.rb +18 -0
  366. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_assignment_user_group.rb +18 -0
  367. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_due.rb +18 -0
  368. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_priority_level.rb +18 -0
  369. data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -0
  370. data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -0
  371. data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -0
  372. data/lib/ecoportal/api/graphql/input.rb +8 -0
  373. data/lib/ecoportal/api/graphql/interface/base_page.rb +100 -58
  374. data/lib/ecoportal/api/graphql/interface/location_structure/nodes.rb +27 -28
  375. data/lib/ecoportal/api/graphql/logic/base_model.rb +2 -0
  376. data/lib/ecoportal/api/graphql/logic/base_query.rb +45 -2
  377. data/lib/ecoportal/api/graphql/logic/input.rb +15 -0
  378. data/lib/ecoportal/api/graphql/model/ai_summary_version.rb +10 -0
  379. data/lib/ecoportal/api/graphql/model/organization.rb +65 -55
  380. data/lib/ecoportal/api/graphql/model/page/basic.rb +14 -0
  381. data/lib/ecoportal/api/graphql/model/page/phased.rb +82 -20
  382. data/lib/ecoportal/api/graphql/model/page.rb +1 -0
  383. data/lib/ecoportal/api/graphql/model/page_union.rb +21 -0
  384. data/lib/ecoportal/api/graphql/model/pages_workflow.rb +20 -0
  385. data/lib/ecoportal/api/graphql/model/preset_view.rb +10 -0
  386. data/lib/ecoportal/api/graphql/model/preview_page.rb +10 -0
  387. data/lib/ecoportal/api/graphql/model/register.rb +10 -0
  388. data/lib/ecoportal/api/graphql/model.rb +8 -0
  389. data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -0
  390. data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -0
  391. data/lib/ecoportal/api/graphql/mutation/ai_summary.rb +13 -0
  392. data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_jobs.rb +43 -0
  393. data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_workflows.rb +43 -0
  394. data/lib/ecoportal/api/graphql/mutation/kickstand/fail_job.rb +39 -0
  395. data/lib/ecoportal/api/graphql/mutation/kickstand/fail_workflow.rb +39 -0
  396. data/lib/ecoportal/api/graphql/mutation/kickstand/start_job.rb +39 -0
  397. data/lib/ecoportal/api/graphql/mutation/kickstand/start_workflow.rb +39 -0
  398. data/lib/ecoportal/api/graphql/mutation/kickstand/stop_workflow.rb +39 -0
  399. data/lib/ecoportal/api/graphql/mutation/kickstand.rb +18 -0
  400. data/lib/ecoportal/api/graphql/mutation/location_structure/apply_commands.rb +3 -3
  401. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/add_commands.rb +2 -2
  402. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/create.rb +2 -2
  403. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/drop_bad_commands.rb +3 -3
  404. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/publish.rb +3 -3
  405. data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -0
  406. data/lib/ecoportal/api/graphql/mutation/page/archive.rb +40 -0
  407. data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +40 -0
  408. data/lib/ecoportal/api/graphql/mutation/page/build_from_template.rb +50 -0
  409. data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +40 -0
  410. data/lib/ecoportal/api/graphql/mutation/page/create_from_template.rb +43 -0
  411. data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +40 -0
  412. data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +69 -0
  413. data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +51 -0
  414. data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +40 -0
  415. data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -0
  416. data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -0
  417. data/lib/ecoportal/api/graphql/mutation/page/unarchive.rb +40 -0
  418. data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -0
  419. data/lib/ecoportal/api/graphql/mutation/page/update.rb +40 -0
  420. data/lib/ecoportal/api/graphql/mutation/page/update_variable_bindings.rb +44 -0
  421. data/lib/ecoportal/api/graphql/mutation/page.rb +28 -0
  422. data/lib/ecoportal/api/graphql/mutation/preset_view/create.rb +35 -0
  423. data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +35 -0
  424. data/lib/ecoportal/api/graphql/mutation/preset_view/permission.rb +37 -0
  425. data/lib/ecoportal/api/graphql/mutation/preset_view/update.rb +35 -0
  426. data/lib/ecoportal/api/graphql/mutation/preset_view.rb +15 -0
  427. data/lib/ecoportal/api/graphql/mutation/register/create.rb +35 -0
  428. data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -0
  429. data/lib/ecoportal/api/graphql/mutation/register/update.rb +35 -0
  430. data/lib/ecoportal/api/graphql/mutation/register.rb +14 -0
  431. data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -0
  432. data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -0
  433. data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -0
  434. data/lib/ecoportal/api/graphql/mutation/template/create.rb +39 -0
  435. data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -0
  436. data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +43 -0
  437. data/lib/ecoportal/api/graphql/mutation/template/publish.rb +39 -0
  438. data/lib/ecoportal/api/graphql/mutation/template/unpublish.rb +39 -0
  439. data/lib/ecoportal/api/graphql/mutation/template/update.rb +43 -0
  440. data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +43 -0
  441. data/lib/ecoportal/api/graphql/mutation/template.rb +18 -0
  442. data/lib/ecoportal/api/graphql/mutation.rb +8 -0
  443. data/lib/ecoportal/api/graphql/payload/ai_summary_generate.rb +12 -0
  444. data/lib/ecoportal/api/graphql/payload/execute_workflow_commands.rb +36 -0
  445. data/lib/ecoportal/api/graphql/payload/force_commands.rb +31 -0
  446. data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_jobs.rb +36 -0
  447. data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_workflows.rb +36 -0
  448. data/lib/ecoportal/api/graphql/payload/kickstand/job.rb +13 -0
  449. data/lib/ecoportal/api/graphql/payload/kickstand/workflow.rb +13 -0
  450. data/lib/ecoportal/api/graphql/payload/kickstand.rb +15 -0
  451. data/lib/ecoportal/api/graphql/payload/location_structure/draft/create.rb +33 -34
  452. data/lib/ecoportal/api/graphql/payload/ok_payload.rb +21 -0
  453. data/lib/ecoportal/api/graphql/payload/page/archive.rb +13 -0
  454. data/lib/ecoportal/api/graphql/payload/page/build_from_template.rb +13 -0
  455. data/lib/ecoportal/api/graphql/payload/page/create_from_template.rb +13 -0
  456. data/lib/ecoportal/api/graphql/payload/page/draft.rb +13 -0
  457. data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -0
  458. data/lib/ecoportal/api/graphql/payload/page/unarchive.rb +13 -0
  459. data/lib/ecoportal/api/graphql/payload/page/update.rb +13 -0
  460. data/lib/ecoportal/api/graphql/payload/page/update_variable_bindings.rb +13 -0
  461. data/lib/ecoportal/api/graphql/payload/page.rb +19 -0
  462. data/lib/ecoportal/api/graphql/payload/preset_view.rb +11 -0
  463. data/lib/ecoportal/api/graphql/payload/register.rb +11 -0
  464. data/lib/ecoportal/api/graphql/payload/template/create.rb +13 -0
  465. data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +13 -0
  466. data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +13 -0
  467. data/lib/ecoportal/api/graphql/payload/template/publish.rb +13 -0
  468. data/lib/ecoportal/api/graphql/payload/template/unpublish.rb +13 -0
  469. data/lib/ecoportal/api/graphql/payload/template/update.rb +13 -0
  470. data/lib/ecoportal/api/graphql/payload/template/update_information.rb +13 -0
  471. data/lib/ecoportal/api/graphql/payload/template.rb +18 -0
  472. data/lib/ecoportal/api/graphql/payload.rb +11 -0
  473. data/lib/ecoportal/api/graphql/query/action.rb +1 -1
  474. data/lib/ecoportal/api/graphql/query/action_categories.rb +1 -1
  475. data/lib/ecoportal/api/graphql/query/actions.rb +2 -2
  476. data/lib/ecoportal/api/graphql/query/contractor_entities.rb +1 -1
  477. data/lib/ecoportal/api/graphql/query/file_upload_signature.rb +76 -0
  478. data/lib/ecoportal/api/graphql/query/location_structure/draft.rb +2 -2
  479. data/lib/ecoportal/api/graphql/query/location_structure.rb +1 -1
  480. data/lib/ecoportal/api/graphql/query/location_structures.rb +1 -1
  481. data/lib/ecoportal/api/graphql/query/page.rb +45 -0
  482. data/lib/ecoportal/api/graphql/query/page_delta.rb +47 -0
  483. data/lib/ecoportal/api/graphql/query/page_with_forces.rb +43 -0
  484. data/lib/ecoportal/api/graphql/query/pages.rb +59 -0
  485. data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +59 -0
  486. data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -0
  487. data/lib/ecoportal/api/graphql/query/register_preview_pages.rb +83 -0
  488. data/lib/ecoportal/api/graphql/query/templates.rb +53 -0
  489. data/lib/ecoportal/api/graphql/query.rb +11 -0
  490. data/lib/ecoportal/api/graphql.rb +60 -2
  491. data/lib/ecoportal/api/graphql_version.rb +5 -5
  492. data/scripts/auto-worker-scheduler.sh +386 -0
  493. data/tests/contractor_entity_create.rb +19 -19
  494. data/tests/contractor_entity_udpate.rb +20 -20
  495. data/tests/dump_page_model.rb +74 -0
  496. data/tests/loc_structure_get.rb +1 -2
  497. data/tests/loc_structure_update.rb +51 -51
  498. data/tests/loc_structures_get.rb +15 -15
  499. 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