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,230 @@
1
+ # GraphQL Domain Knowledge — Agent Reference
2
+
3
+ *Accumulated from developer context, backend source code, and production Insomnia queries.*
4
+ *This document is for AI agents. Read it before analysing or generating any GraphQL query or mutation.*
5
+
6
+ ---
7
+
8
+ ## GraphQL is a Language — Not a Fixed REST Schema
9
+
10
+ **Critical for agents:** GraphQL queries are client-defined. The same server resource can be
11
+ queried in infinitely many ways — with different field selections, variable names, fragment
12
+ compositions, and directive combinations. Variability between queries is **intentional and
13
+ expected**, not an error. Do not assume a query "should look like" any specific previous example.
14
+
15
+ Specific implications:
16
+ - **Multiple operations per request** are valid GraphQL. A single HTTP request can contain
17
+ multiple `query` or `mutation` definitions, separated by blank lines. The server executes
18
+ them in declaration order. The `operationName` field in the JSON payload identifies which
19
+ named definition is the primary one. This is used when a stateless client middleware cannot
20
+ sequence separate requests (e.g. `ArchivePage` blanks `externalId` then archives, in one call).
21
+ - **Fragment composition varies** — one team member's query may inline all fields; another's
22
+ uses the shared `CommonPageUnion` fragment. Both are correct.
23
+ - **Variable names are arbitrary** — `$search`, `$searchConf`, `$conf` all refer to the
24
+ same `Search` argument type.
25
+ - **Field selection is partial** — a query that omits `patchVer` is not wrong; it just
26
+ means the caller doesn't need it for that use case.
27
+
28
+ ---
29
+
30
+ ## Backend Naming Conventions — Rosetta Stone
31
+
32
+ The same concept appears under different names depending on which layer you're looking at.
33
+ This causes confusion when reading source code alongside API output.
34
+
35
+ | Concept | Backend Enzyme (old) | Backend NewEp (current) | APIv2 endpoint | GraphQL endpoint |
36
+ |---|---|---|---|---|
37
+ | Workflow phases | `stages` | `stages` | `stages` | `stages` |
38
+ | Content containers within a stage | `flow_nodes` | `sections` | `sections` | `sections` |
39
+ | Content fields within a section | `membranes` | `dataFields` | `components` | `dataFields` |
40
+
41
+ **Note:** Enzyme is the original Rails backend namespace. NewEp is the revamp that the team
42
+ is progressively migrating to. React front-end components that have been migrated to React
43
+ use GraphQL; anything still on AngularJS uses the old APIv2. A model being available via
44
+ GraphQL implies it has a NewEp backend class.
45
+
46
+ ---
47
+
48
+ ## The Sorter `key` Field — aka `ref` — aka the Future Field ID
49
+
50
+ The `key` field in searchConf sorters (and similarly in many filter params) is NOT the
51
+ MongoDB `id` of the field. It is the **field's ES index key**, also called the `ref` property
52
+ in the backend model (`Enzyme::Membrane` / `NewEp::DataField`).
53
+
54
+ **Composition of `ref`:**
55
+ ```
56
+ <type_shorthand>.<truncated_hash_of_label>
57
+ ```
58
+
59
+ - `type_shorthand`: e.g. `date`, `gauge`, `files`, `select_str`, `select_num`,
60
+ `rich_text`, `plain_text`, `actions_list`
61
+ - `.` separator
62
+ - Hash of the label name (truncated when label > 3 characters)
63
+ - Formula: `Ecoportal::API::Common::Content::StringDigest` in `ecoportal-api-v2`
64
+
65
+ **Why this matters:**
66
+ - This `ref` is how Elasticsearch identifies and indexes the field
67
+ - It is used in: dashboard charts, filter configs, cross-register configs (linked fields,
68
+ visitor management), exports
69
+ - A field's `ref` is stable as long as its label doesn't change
70
+ - The team refers to this as the "Field ID" — a future project aims to formalise it as a
71
+ proper System ID (as opposed to the internal MongoDB `id`)
72
+
73
+ **Known problem:** When tech scripts added fields to existing pages without copying from
74
+ the template (e.g. bulk backfills), fields were created with random generation-time IDs
75
+ rather than inheriting the template's `ref`. This creates duplicate/conflicting refs and
76
+ causes issues in exports (missing CSV values) and some chart types.
77
+
78
+ ---
79
+
80
+ ## The Genome Signature — ⚠️ IMPORTANT — Will Be Revisited
81
+
82
+ **Record this carefully. This topic will be picked up again.**
83
+
84
+ The genome signature is an identifier designed to pair fields across pages and their
85
+ source templates. It enables knowing that "this field in page X is the same field as
86
+ this field in template Y", even after the page was created, cloned, or restored.
87
+
88
+ **Original use:** The `Migrator` Rails Service (under the Rails Services loadable path)
89
+ had a full solution using the genome to pair fields during template changes and page
90
+ restorations (backup to YAML + restore).
91
+
92
+ **Current state:**
93
+ - Mostly **unused** — the Migrator solution was abandoned due to maintenance cost and
94
+ low usage
95
+ - **Not indexed** — the genome is not sent to Elasticsearch
96
+ - **Unpaired on many pages** — tech scripts that added fields without copying from the
97
+ template introduced fields with random creation-time genome signatures, breaking the
98
+ pairing with the template
99
+ - **NewEp port status:** Uncertain — unclear whether the NewEp namespace fully ported
100
+ the Genome functionality from the Enzyme namespace
101
+ - The genome is NOT currently exploitable for scripting or integration purposes
102
+
103
+ **Why it matters for the future:** The genome was the intended foundation for:
104
+ - Automatically applying template changes to existing pages
105
+ - Correctly pairing fields across page versions for cross-register configurations
106
+ - The Field ID project (see above) may supersede or build on this concept
107
+
108
+ ---
109
+
110
+ ## DataField Input — The `dataFields.updates` Structure
111
+
112
+ When creating or updating page data fields via `createFromTemplate` or `updatePage`,
113
+ field updates use a typed union pattern in `dataFields.updates`:
114
+
115
+ ```json
116
+ {
117
+ "dataFields": {
118
+ "updates": [
119
+ { "date": { "id": "FIELD_ID", "value": "2024-11-25T08:30+13:00" } },
120
+ { "people": { "id": "FIELD_ID", "peopleIds": ["PERSON_ID"] } },
121
+ { "plainText": { "id": "FIELD_ID", "value": "JOB-0777" } },
122
+ { "richText": { "id": "FIELD_ID", "content": "<p>HTML content</p>" } },
123
+ { "select": { "id": "FIELD_ID", "options": [{ "id": "OPT_ID", "selected": true }] } },
124
+ { "geo": { "id": "FIELD_ID", "address": "...", "coordinates": { "lat": -36.86, "lon": 174.76 } } }
125
+ ]
126
+ }
127
+ }
128
+ ```
129
+
130
+ The `id` in each field update is the **data field's MongoDB ObjectId** (from the built
131
+ draft — obtained via `buildFromTemplate`), not the `ref`/Field ID.
132
+
133
+ ---
134
+
135
+ ## Filter Rules — Silent Failure Traps
136
+
137
+ **snake_case is mandatory for filter operations.** If a CamelCase operation name is used
138
+ (e.g. `ExactFilter` instead of `exact_filter`), the server returns no error — it silently
139
+ returns zero results. This is one of the hardest bugs to diagnose for customers.
140
+
141
+ **`filters` should always be an Array.** The server normalises a single filter object to
142
+ an array internally, so both formats work. However, always use the array form to be
143
+ consistent and avoid edge cases.
144
+
145
+ ---
146
+
147
+ ## Compound Mutations — Multiple Operations Per Request
148
+
149
+ GraphQL allows combining multiple mutations in a single request. They execute in
150
+ declaration order on the server. Use cases:
151
+
152
+ 1. **Stateless middleware:** When a client framework cannot maintain session state between
153
+ requests, combining dependent mutations into one request guarantees correct sequencing.
154
+ Example: `ArchivePage` blanks `externalId` first (to avoid duplicate ID conflicts on
155
+ unarchive+recreate), then calls `archivePage`.
156
+
157
+ 2. **Atomic-ish operations:** While GraphQL doesn't provide true ACID transactions, running
158
+ mutations in one request at least avoids partial state where a network failure between
159
+ two separate requests could leave data in an inconsistent state.
160
+
161
+ The `operationName` field in the HTTP JSON payload names the primary definition. In a
162
+ compound mutation, this is conventionally the name of the encompassing `mutation` block.
163
+
164
+ ---
165
+
166
+ ## Template vs Instance Pages
167
+
168
+ - **Template** (`templatesOnly: true` in org search): Source pages that define field
169
+ structure. Used with `buildFromTemplate` / `createFromTemplate` workflows.
170
+ - **Instance page**: A page created from a template. Field IDs are assigned by the DB
171
+ on creation — they must be fetched (via `buildFromTemplate` result or subsequent org
172
+ search) before constructing field update inputs.
173
+ - **`showHiddenData: Boolean`**: Controls whether fields marked as hidden in the template
174
+ are included in query results. Set to `true` for integration scripts that need all fields.
175
+
176
+ ---
177
+
178
+ ## Stage Rendering: APIv2 vs GraphQL (critical)
179
+
180
+ **APIv2:** Instance pages (pages WITH stages) are rendered and saved ONE STAGE PER REQUEST.
181
+ `pages.get(id, stage_id: id)` returns a single-stage view. This was an architectural
182
+ limitation. Templates and stageless pages are the only ones rendered in full by v2.
183
+
184
+ **GraphQL:** Full page in one request. `page(id:)` returns all stages, all sections,
185
+ all data fields the user has access to. Can update all stages at once, OR target a
186
+ specific stage via `stageId: ID` in `UpdatePageInput`.
187
+
188
+ **Stage-specific operations that REQUIRE `stageId`:**
189
+ - Stage close-out / submit (triggers fill-in task creation on next stage)
190
+ - Stage review sign-off (task completion triggers stage N+1 activation)
191
+ - People fields scoped to a specific stage (permission grants are stage-scoped)
192
+ Without `stageId`, these operations may succeed but not trigger the expected workflows.
193
+
194
+ **Stage permissions in GraphQL:** No access errors — stages the user cannot access
195
+ are simply absent from the response. APIv2 returned explicit errors per stage.
196
+ Scripts that relied on stage-access errors need to check for stage presence instead.
197
+
198
+ **Template updates:** Engineering explicitly does NOT want templates updated via GraphQL
199
+ while the AngularJS template editor is still in use. Block template mutation endpoints.
200
+ Read-only template queries (`templates(...)`) are fine.
201
+
202
+ ---
203
+
204
+ ## Forces — Being Replaced by Workflow Builder
205
+
206
+ **Forces** are AngularJS front-end snippets that attach to specific stages via bindings.
207
+ They implement conditional field behaviour (show/hide sections, computed values, risk
208
+ matrices). Forces are NOT accessible or meaningful via GraphQL. Low scripting priority.
209
+
210
+ **Workflow Builder** (`pagesWorkflow`) is the replacement:
211
+ - Deployed: notifications, automated page creation, task assignments
212
+ - NOT yet deployed: conditional field/section workflows (show this section only when
213
+ field X has value Y; compute gauge value from severity × likelihood matrix)
214
+ - Recently refactored — active development, high change velocity, quick CI/CD fixes
215
+ - Do NOT design scripting against workflow builder internals — API is unstable
216
+
217
+ **Implication for scripts:** Workflow triggers (task creation, stage transitions) fire
218
+ as server-side side effects of `updatePage`. Scripts should check mutation payloads for
219
+ unexpected errors, but should not attempt to directly manage workflow state.
220
+
221
+ ---
222
+
223
+ ## Two Build Patterns — Legacy vs Standard
224
+
225
+ **Build - Model (legacy):** Uses own inline fragment definitions per query. No shared
226
+ `CommonPageUnion`. Still in use in some older Insomnia requests. Avoid in new code.
227
+
228
+ **Build - Standard (current):** Uses `CommonPageUnion` with `$fields/$content/$only_content`
229
+ variables. This is the team-wide standard — designed for customer-facing documentation
230
+ and team knowledge sharing. New scripts should use this pattern.
@@ -0,0 +1,71 @@
1
+ # Review: DataField read/write shape asymmetry + dirty-detection baseline
2
+
3
+ **Status:** OPEN — needs a thorough, dedicated review pass.
4
+ **Raised:** 2026-06-30 (after a real bug found + fixed on `CrossReference`).
5
+ **Owner cue:** Oscar flagged this for thorough review.
6
+
7
+ ---
8
+
9
+ ## The bug class
10
+
11
+ Several `DataField` subtypes are **READ** from the GraphQL response under one key/shape and
12
+ **WRITTEN** (staged for mutation) under a *different* key/shape. This breaks two things:
13
+
14
+ 1. **Reading current values** — the typed reader reads the WRITE key, which the read response
15
+ never populates → returns `[]` (or stale) on a freshly-fetched page.
16
+ 2. **Dirty detection** — the default leaf diff compares `doc` vs `original_doc` at the key
17
+ level. A write adds a brand-new key (e.g. `pages`) absent from the read shape, so the field
18
+ looks dirty the moment any setter runs — even when the effective value is unchanged
19
+ (e.g. `clear` on an already-empty reference → phantom `referenceIds: []`). Under APIv2 the
20
+ equivalent no-op was correctly not-dirty; this is a v2-parity regression.
21
+
22
+ ## Per-field audit (2026-06-30)
23
+
24
+ | Field | READ (fragment `common_page_union.rb`) | WRITE key | Reader reads | Verdict |
25
+ |---|---|---|---|---|
26
+ | `CrossReference` | `references { nodes { id … } }` | `pages` | refs→pages | **FIXED** (set-based `dirty?` vs `original_doc`; shared `ref_ids_from`) |
27
+ | `FileField` | `items { fileContainer { id … } }` | `fileContainers` | `fileContainers` | **BROKEN reader** (reads key the response never has → `[]`) + phantom-dirty |
28
+ | `ImageGallery` | `images { downloadUrl, caption … }` (no `id` even fetched) | `fileContainers` | `fileContainers` | **BROKEN reader** + phantom-dirty; read fragment also omits ids |
29
+ | `ContractorEntities` | `contractorEntities { id, name … }` | `contractors` | `contractors` | **BROKEN reader** + phantom-dirty |
30
+ | `TagField` | `locations { …locationNode }` | `locations` | `locations` | key matches, but write narrows shape (`{id,name,…}`→`{id}`); `as_input` is nil (page-level locations) → low impact, still verify |
31
+ | `People`, `Select`, `Checklist`, `PlainText`, `RichText`, `Number`, `Gauge`, `DateField`, `Geo` | read key == write key | same | same | OK (no asymmetry) |
32
+
33
+ ## Recommended review/fix direction
34
+
35
+ Two layers, decide per field:
36
+
37
+ - **Reader correctness:** make `*_ids` read the actual response key (`items[].fileContainer.id`,
38
+ `images[].id` (needs the fragment to fetch it), `contractorEntities[].id`) — falling back to
39
+ the staged write key when present (so post-write reads reflect the staged value). The shared
40
+ `ref_ids_from(doc)` pattern in `cross_reference.rb` is the template.
41
+ - **Dirty baseline:** any custom `dirty?`/`as_input` MUST diff the effective value against
42
+ **`original_doc`** (the diff `prev_doc` — see `Diffable::ClassicDiffService#prev_doc`), NOT
43
+ `doc`. Better still, where shapes can be normalised, defer to the inherited `DiffService`
44
+ rather than hand-rolling. Add a `consolidate!`-cycle assertion to every such field's spec
45
+ (read → mutate → as_input → `consolidate!` → re-check `dirty? == false`).
46
+
47
+ These fields are NOT exercised by the 3 cutover use cases (toocs = CrossReference + People;
48
+ cans = PlainText + Number + Select), so this is not a 1-Jul blocker — but it is a real
49
+ correctness gap for full parity and any script touching files/images/contractors.
50
+
51
+ ## Retro — why the `CrossReference` bug went unnoticed (so we don't repeat it)
52
+
53
+ 1. **Hand-rolled the baseline from `doc` instead of `original_doc`.** I wrote a custom `dirty?`
54
+ from first principles instead of reading the existing diff contract first. Had I checked
55
+ `ClassicDiffService#prev_doc` (returns `original_doc`) before writing it, I'd have used the
56
+ right baseline. Root cause: built a new abstraction parallel to an existing one without
57
+ consulting it.
58
+ 2. **Incidental-correctness masked it in tests AND live.** My reader and my baseline both read
59
+ `references.nodes` from `doc`; since the setter only writes `pages`, the two always agreed on
60
+ the fetch-then-mutate path — the only path the specs and the live run exercised. The error is
61
+ invisible until `consolidate!` puts `pages` into `original_doc`.
62
+ 3. **No lifecycle (`consolidate!`) test.** The test matrix covered read→mutate but not
63
+ read→mutate→update→consolidate→re-diff, where the bug actually surfaces.
64
+ 4. **Conflated "read key isn't mutated" with "doc == baseline".** True premise, wrong
65
+ conclusion. Fragile coupling to current setter behaviour.
66
+
67
+ **Guardrails going forward:**
68
+ - Never compute a dirty baseline from `doc`; use `original_doc` or the `DiffService`.
69
+ - Every field with a staged-write setter gets a `consolidate!`-cycle spec.
70
+ - When adding a typed field, cross-check the fragment READ key against the setter WRITE key;
71
+ if they differ, normalise reads and baseline dirty on `original_doc`.
@@ -0,0 +1,251 @@
1
+ # Refactoring Opportunities
2
+
3
+ Low-priority items noted for future consideration. Not blocking current work.
4
+
5
+ ---
6
+
7
+ ## [LOW] Review graphlient DSL for future method chaining support
8
+
9
+ **Logged:** 2026-06-13
10
+ **Upstream repo:** `C:\ruby_scripts\git\graphlient` — branch `feature/directives-dsl-support`
11
+
12
+ **Context:** The current graphlient DSL passes directives as arguments:
13
+ ```ruby
14
+ spread :InvoiceFields, _skip(if: :skip_invoice)
15
+ on(:DraftInvoice, _skip(if: :skip_drafts)) { draft_id }
16
+ ```
17
+
18
+ A fluent chain-style equivalent (`spread(:InvoiceFields).skip(if: :x)`) would require
19
+ DSL methods to return a builder object rather than appending to `@query_str` and returning
20
+ `nil`. The current approach (directives as arguments) does **not block** future chaining —
21
+ both can coexist as alternative interfaces.
22
+
23
+ **Before the next graphlient upstream PR:** verify no API decisions in the current MR
24
+ force a hard dependency on the argument-passing style that would make a chain builder
25
+ impossible to introduce later without a breaking change.
26
+
27
+ **When to revisit:** When MR #116 is merged upstream. Low urgency — chaining is optional,
28
+ current syntax is functional.
29
+
30
+ ---
31
+
32
+ ## [LOW] Fragment definitions as `.graphql` files instead of Ruby HEREDOCs
33
+
34
+ **Logged:** 2026-06-05
35
+ **Files affected:** `lib/ecoportal/api/graphql/fragment/*.rb`
36
+
37
+ **Idea:** Define named fragments in `.graphql` files (e.g.
38
+ `lib/ecoportal/api/graphql/fragment/action.graphql`) and load them at startup via
39
+ `File.read`, rather than embedding them as Ruby HEREDOCs.
40
+
41
+ **Benefits:**
42
+ - GraphQL files get syntax highlighting and editor tooling (linting, autocomplete).
43
+ - Cleaner separation — `.graphql` is the DSL, `.rb` is the Ruby wrapper.
44
+ - Easier to diff fragment changes in git without Ruby noise.
45
+
46
+ **Open questions / why it's low priority:**
47
+ - HEREDOCs work fine and there is no string interpolation in fragments, so the
48
+ only gain is cosmetic/tooling.
49
+ - Client scripts may define custom queries as HEREDOCs in their own code. Whether
50
+ those should auto-load into the fragment registry (and how) is unresolved.
51
+ `FragmentDefinitions.assemble(*syms)` only knows about fragments registered via
52
+ `fragment :Sym, string` — any `.graphql` file loader would need to register them
53
+ the same way.
54
+ - Until the auto-loading story for custom script fragments is clear, standardising
55
+ on `.graphql` files for the gem's own fragments may create inconsistency.
56
+
57
+ **When to revisit:** Once there is a concrete script use case that benefits from
58
+ `.graphql` file loading, or when editor tooling for HEREDOCs becomes a pain point.
59
+
60
+ ---
61
+
62
+ ## [MEDIUM] Upstream-stack friction points — candidates for future graphql-client / graphlient MRs
63
+
64
+ **Logged:** 2026-06-16
65
+ **Scope:** `graphql-client` (fork: `rellampec/graphql-client`) and `graphlient` (fork: `rellampec/graphlient`)
66
+ **Origin:** Adoption-friction analysis while documenting inline named fragments (graphql-client
67
+ issue [#76](https://github.com/github-community-projects/graphql-client/issues/76)).
68
+
69
+ These are the genuine reasons a large Ruby shop tends to avoid or rip out the
70
+ `graphlient → graphql-client → graphql-ruby → faraday` stack. None is confirmed as the
71
+ cause of any specific org's removal (see note below); they are the real ergonomic/architectural
72
+ frictions worth addressing if we want the forks to be community-scalable.
73
+
74
+ 1. **Layered indirection for an HTTP POST.** Four abstraction layers to send a query string
75
+ and parse JSON. *MR angle:* keep graphlient a genuinely thin, optional convenience layer;
76
+ don't deepen coupling. Make the "drop graphlient, use graphql-client directly" path easy.
77
+
78
+ 2. **Heavy/opinionated dependency footprint.** graphql-client pulls in **ActiveSupport**
79
+ (only for `constantize`/Inflector at `lib/graphql/client.rb:161`) and the *full*
80
+ **graphql-ruby** gem to act as a client. *MR angle:* scope down the ActiveSupport surface
81
+ (the Inflector use is small and replaceable) — a frequent complaint for client-only use.
82
+
83
+ 3. **Design-philosophy mismatch — forced component isolation.** The constant-bound,
84
+ colocated-fragment model raises `ImplicitlyFetchedFieldError` on reuse-only fragments.
85
+ This is exactly issue #76. *MR angle:* the inline-named-fragment convention (documented)
86
+ + the planned anchored-regex parse fix together give a sanctioned "fragments for pure
87
+ reuse" path. Land both.
88
+
89
+ 4. **Client-side schema coupling.** Parse-time validation needs a vendored schema artifact
90
+ kept in sync with the server. *MR angle:* document/support a dynamic-query path
91
+ (`allow_dynamic_queries`) clearly so teams that build queries at runtime aren't forced
92
+ into static constants.
93
+
94
+ 5. **Maintenance / bus-factor optics.** graphlient last released 0.8.0 (Jan 2024); graphql-client
95
+ moved to the low-staff `github-community-projects/` org. *MR angle (non-code):* CHANGELOG +
96
+ CONTRIBUTING hygiene (already in flight on `chore/add-changelog-contributing`) and steady,
97
+ well-scoped PRs are the cheapest way to improve adoption confidence.
98
+
99
+ 6. **Parse-step robustness.** The unanchored `str.match(/fragment\s*#{const_name}/)` heuristic
100
+ at `lib/graphql/client.rb:161` misfires on prefix collisions (`...UserFields` vs
101
+ `fragment UserFieldsExtended`) and `\s*` even matches `fragmentUserFields`. *MR angle:*
102
+ the planned **Step 5** anchored fix (`fragment\s+Name\s+on\b`), stacked on the
103
+ characterization specs. Note upstream tried + reverted a fix here once (`c576c2e` →
104
+ `b5a4612`), so characterization-first is the right order.
105
+
106
+ **Note on the "GitLab removed it" premise:** a GitLab.com group search for `graphql-client`
107
+ MRs (2026-06-16) surfaced **dependency *updates*, not removals** — e.g. triage-ops bumping
108
+ graphql-client to `~> 0.26.0` (merged Feb 2026). i.e. GitLab appears to actively maintain the
109
+ dep, the opposite of dropping it. Treat the rendered search as indicative only (may be
110
+ login-gated/partial); no verified evidence of a major-org removal exists. ecoPortal's own
111
+ CI/CD removal was an internal decision, not an industry trend. See memory
112
+ `project-fork-revival-strategy`.
113
+
114
+ **When to revisit:** Items 3 & 6 are active (the #76 docs MR + Step-5 fix). Items 1, 2, 4 are
115
+ future upstream MRs once the fork-revival sequence (hygiene → characterization → parse fix) lands.
116
+
117
+ **Update (2026-06-16):** the fork-revival PRs are OPEN upstream — graphql-client #79 (hygiene),
118
+ #80 (inline-fragment docs), #81 (characterization tests), #82 (the anchored parse fix). #82
119
+ verified RuboCop-clean + tests green locally. See memory `session-handoff-graphql-client-revival`.
120
+
121
+ ---
122
+
123
+ ## [LOW] graphql-client 1.0 (#15) — aligned contribution candidates
124
+
125
+ **Logged:** 2026-06-23
126
+ **Upstream:** `github-community-projects/graphql-client` issue
127
+ [#15](https://github.com/github-community-projects/graphql-client/issues/15) (1.0.0 roadmap,
128
+ rmosolgo, Feb 2024). Triggered by our #76 comment pinging the maintainer.
129
+ **Priority:** LOW for us — opportunistic, only as the fork-revival PRs (#79–#82) progress.
130
+
131
+ **State of the #15 roadmap (verified 2026-06-23 via issue + comments):** rmosolgo listed three
132
+ pre-1.0 items and invited community suggestions ("If anyone has other suggestions for 1.0, please
133
+ share them in a comment below").
134
+
135
+ 1. **Performance audit** (view helpers + runtime) — rmosolgo reported "performance audit work"
136
+ complete (Nov 14 2024). No formal closure; the runtime hot path includes `Client#parse`.
137
+ 2. **Subscription support** — **STILL OPEN** (the one unaddressed 1.0 item), and **genuinely out of
138
+ our lane.** A brief 2026-06-23 detour considered that the upcoming ecoPortal webhooks feature
139
+ might use GraphQL subscriptions, but the Confluence draft settles it: webhooks are **outbound
140
+ HTTP POST** (Standard Webhooks / Ed25519), and the "subscription" there is just a
141
+ **webhook-registration entity** CRUD'd via ordinary mutations — NOT a GraphQL `subscription` root
142
+ type. So webhooks do not pull #15 item 2 into scope. The webhook-registration CRUD *is* in our
143
+ lane, but as plain queries/mutations in this gem once it hits the PROD schema — no upstream
144
+ graphql-client change needed. See memory `project-webhooks-subscriptions`. Revisit only if some
145
+ *other* future need exposes a real `subscription` root type.
146
+ 3. **Response metadata in HTTP results** — **DONE** via billybonks' PR #43 (rate-limit headers etc.);
147
+ rmosolgo closed the item Nov 14 2024.
148
+
149
+ So the literal roadmap is mostly closed except subscriptions. The real opening is the maintainer's
150
+ **open invitation for 1.0 suggestions** — a 1.0 is the semver moment to land convention/cleanup
151
+ changes that overlap exactly with our aim. Candidate contributions, each already partly in flight:
152
+
153
+ - **[in flight] Land the parse-robustness fix (#82) as a 1.0 correctness item.** Anchored
154
+ `fragment\s+Name\s+on\b` regex at `client.rb:161`. Touches the "runtime code" the perf audit
155
+ named; converts a crash into a clean `ValidationError`. Cheapest, most defensible 1.0 contribution.
156
+ - **[in flight] Bless "fragments for pure reuse" as a documented 1.0 convention (#76 + #80/#81).**
157
+ Inline named fragments, Apollo-aligned, backwards-compatible (constant-bound isolation kept as the
158
+ opt-in boundary). A behaviour/philosophy clarification is best made at a 1.0 boundary — frame it
159
+ to the maintainer that way.
160
+ - **[new] Scope down the ActiveSupport dependency.** graphql-client pulls full ActiveSupport for
161
+ `constantize`/Inflector around `client.rb:161`. A 1.0 is when you'd drop/loosen a heavy dep;
162
+ classic adoption-friction win (friction item #2 above). Spec out a small internal constantize or
163
+ a narrower require before proposing.
164
+ - **[new] Document/support the dynamic-query path (`allow_dynamic_queries`).** Lets teams build
165
+ queries at runtime without static constants — pairs with the fragments work and the schema-coupling
166
+ friction (item #4 above). Likely docs + a small guard test.
167
+ - **[new] Named operations.** Optional operation names for server-side logging / APM / persisted
168
+ queries — also the natural identifier subscriptions would need later, so it nudges item #2 forward
169
+ without committing us to transports. Couples with the graphlient named-query DSL
170
+ (`query(:GetInvoice)`) already noted in memory `project-graphql-client-redesign`.
171
+
172
+ **Suggested action (low urgency):** once #79–#82 are merged, post a single comment on #15 offering
173
+ the parse-fix + inline-fragment convention + ActiveSupport scope-down as concrete 1.0 suggestions,
174
+ and noting subscriptions are out of our scope (we drive query/mutation over HTTP POST; ecoPortal's
175
+ webhooks are outbound HTTP, not GraphQL subscriptions — see item 2). Do not post before the revival
176
+ PRs land (keeps our credibility — ship the easy ones first).
177
+
178
+ **When to revisit:** after graphql-client #79–#82 merge. See memories
179
+ `project-fork-revival-strategy`, `session-handoff-graphql-client-revival`.
180
+
181
+ ---
182
+
183
+ ## [MEDIUM] graphlient masks client-side graphql-client errors (lost `inner_exception`)
184
+
185
+ **Logged:** 2026-06-16
186
+ **Upstream repo:** `C:\ruby_scripts\git\graphlient` — fork `rellampec/graphlient`, branch `feature/directives-dsl-support`
187
+
188
+ **Problem:** `lib/graphlient/client.rb` rescues `GraphQL::Client::Error` in both `parse` (L19-20)
189
+ and `execute` (L41-42) and re-raises `ClientError, e.message` — passing only the message string.
190
+ graphlient's base `Error#initialize(message, inner_exception = nil)` is built to carry the
191
+ original, and `FaradayServerError` uses it correctly (`super(inner_exception.message, inner_exception)`),
192
+ but these two rescues leave `inner_exception = nil`. Lost: the original error's class and the
193
+ source-location backtrace graphql-client deliberately sets (e.g. `uninitialized constant X`,
194
+ static-validation messages). Related inconsistency: `faraday_adapter.rb:73` passes the exception
195
+ as the *message* arg, so it also leaves `inner_exception` nil.
196
+
197
+ **Fix:** `raise Graphlient::Errors::ClientError.new(e.message, e)` (matches FaradayServerError).
198
+ If the symptom is a *blank* message rather than a lost cause, also audit the
199
+ `GraphQLError`/`ExecutionError#to_s` path (formats from `errors.details`, can render empty).
200
+
201
+ **Note:** user recalls patching BOTH graphql-client and graphlient for this — so there may be a
202
+ graphql-client-side aspect too (the #82 anchoring fix already converts one crash into a clean
203
+ `ValidationError`). Own graphlient PR, separate from #115/#116. Test-first (repro spec asserting
204
+ `inner_exception` populated). Full detail: memory `graphlient-error-masking`.
205
+
206
+ **When to revisit:** after the #116 DSL work; or sooner if error opacity bites again during the
207
+ template-update / Workflow* work.
208
+
209
+ ---
210
+
211
+ ## [VERY LOW] Extract `SnakeCamelIndifferentAccess` into its own gem
212
+
213
+ **Logged:** 2026-06-05
214
+ **Origin:** `eco-helpers` — `lib/eco/data/hashes/sanke_camel_indifferent_access.rb`
215
+ (note: filename has a typo — `sanke` instead of `snake`)
216
+ **Specs:** already exist in `eco-helpers` spec suite
217
+
218
+ **Idea:** Extract `Eco::Data::Hashes::SnakeCamelIndifferentAccess` (and its dependencies
219
+ `Eco::Data::Strings::SnakeCase`, `Eco::Data::Strings::CamelCase`) into a standalone gem.
220
+ Then use it in `ecoportal-api-graphql`'s base model so that GraphQL response docs (stored
221
+ internally in camelCase as the API returns them) are also accessible via snake_case Ruby
222
+ methods — without a full key rename on every read.
223
+
224
+ **What the module does:**
225
+ Hash accessor that treats `fooBar` and `foo_bar` as the same key. Configurable preferred
226
+ form (defaults to `:snake_case`). Operations: `get`, `set`, `merge!`, `each`, `slice`,
227
+ `camelize_keys`, `snakeize_keys`, all case-indifferent. Uses round-trip conversion via
228
+ `snake_case(camelCase) ↔ camelCase(snake_case)`.
229
+
230
+ **Why this matters for the GraphQL gem:**
231
+ Currently `ecoportal-api-graphql` models map every field explicitly via `passthrough`,
232
+ `passboolean`, etc. using camelCase names matching the API (e.g. `passthrough :patchVer`).
233
+ Ruby callers must use `model.patchVer`. With indifferent access, `model.patch_ver` and
234
+ `model.patchVer` would both work without any change to the field declarations. This
235
+ eliminates the Ruby style inconsistency that has been a known pain point for 4+ years.
236
+
237
+ **Proposed gem name:** `hash_case_indifferent` or `camel_snake_hash` (TBD)
238
+
239
+ **Work involved:**
240
+ 1. Extract module + string helpers + specs into new gem repo
241
+ 2. Add gem as dependency of `eco-helpers` and `ecoportal-api-graphql`
242
+ 3. Include in `Common::GraphQL::Model` (or `Logic::BaseModel`) so all model
243
+ instances support both `model.patchVer` and `model.patch_ver`
244
+ 4. Verify no regressions in downstream `eco-helpers` scripts that use camelCase access
245
+
246
+ **Prerequisite:** `as_update`/`as_input` pipeline (Group 4) should be stable first,
247
+ since that pipeline reads field values off models. Once indifferent access is in place,
248
+ the diff/input code may need to handle both key forms for lookups.
249
+
250
+ **When to revisit:** After Group 4 + Page model Phase A/B are stable. New project
251
+ milestone, not a small refactor.