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,242 @@
1
+ # Data Fields — Code Spec
2
+
3
+ *All 20 DataField types, access patterns, mutation input serialisation, and Collection API.*
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ Data fields are the form inputs within a page (rich content — not structural metadata).
10
+ Each field is a live instance with a MongoDB ObjectId (`id`) assigned on page creation.
11
+
12
+ All field types inherit from `Base::Page::DataField < Logic::BaseModel` and live in
13
+ `lib/ecoportal/api/graphql/base/page/data_field/`.
14
+
15
+ ---
16
+
17
+ ## All 20 Field Types
18
+
19
+ | GraphQL `__typename` | Ruby class | Writable | Input key |
20
+ |---|---|---|---|
21
+ | `PlainText` | `DataField::PlainText` | ✓ | `plainText` |
22
+ | `RichText` | `DataField::RichText` | ✓ | `richText` |
23
+ | `Date` | `DataField::DateField` | ✓ | `date` |
24
+ | `Number` | `DataField::Number` | ✓ | `number` |
25
+ | `Gauge` | `DataField::Gauge` | ✓ | `gauge` |
26
+ | `Select` | `DataField::Select` | ✓ | `select` |
27
+ | `Checklist` | `DataField::Checklist` | ✓ | `checklist` |
28
+ | `TagField` | `DataField::TagField` | ✓ | `locationField` |
29
+ | `People` | `DataField::People` | ✓ | `people` |
30
+ | `Geo` | `DataField::Geo` | ✓ | `geo` |
31
+ | `ContractorEntities` | `DataField::ContractorEntities` | ✓ | `contractorEntities` |
32
+ | `CrossReference` | `DataField::CrossReference` | ✓ | `crossReference` |
33
+ | `File` | `DataField::FileField` | ✓* | `file` |
34
+ | `ImageGallery` | `DataField::ImageGallery` | ✓* | `imageGallery` |
35
+ | `Signature` | `DataField::Signature` | — | — |
36
+ | `Mailbox` | `DataField::Mailbox` | — | — |
37
+ | `ActionsList` | `DataField::ActionsList` | — | — |
38
+ | `Law` | `DataField::Law` | — | — |
39
+ | `AiSummary` | `DataField::AiSummary` | — | — |
40
+ | `Table` | `DataField::Table` | — | — |
41
+
42
+ \* File and ImageGallery require a prior file upload to the REST upload endpoint to obtain
43
+ `fileContainerIds`. See `projects/TODO.md` — "File upload" item.
44
+
45
+ ---
46
+
47
+ ## Dispatch
48
+
49
+ `DataField.from_doc(raw_hash)` reads `__typename` (or `type` if already normalised) and
50
+ returns the right subclass instance. Falls back to base `DataField` for unknown types.
51
+
52
+ ```ruby
53
+ field = Base::Page::DataField.from_doc({ '__typename' => 'PlainText', 'id' => 'f1', 'value' => 'X' })
54
+ # => #<DataField::PlainText ...>
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Field Access on Page Models
60
+
61
+ `Interface::BasePage` includes `Concerns::DataFieldAccess`, so all concrete page models have:
62
+
63
+ ```ruby
64
+ page.field_collection # → DataField::Collection (memoized)
65
+ page.components # → same (v2 compat alias)
66
+ page.data_fields_updates # → [DataFieldInput, ...] for dirty fields
67
+ page.data_fields_additions # → [DataFieldInput, ...] for added fields
68
+ page.data_fields_deletions # → [field_id, ...] queued for deletion
69
+ page.data_fields_dirty? # → Boolean
70
+ ```
71
+
72
+ `Base::Page::Phased::Stage` also exposes `components` (via `sections.components`) for
73
+ stage-level field access.
74
+
75
+ ---
76
+
77
+ ## DataField::Collection API
78
+
79
+ ```ruby
80
+ coll = page.components
81
+
82
+ # Read
83
+ coll.get_by_type(:plain_text) # → [DataField::PlainText, ...]
84
+ coll.get_by_type('PlainText') # → same (string also works)
85
+ coll.get_by_name('Summary') # → DataField::PlainText (case-insensitive)
86
+
87
+ # Iterate
88
+ coll.each { |f| puts f.label }
89
+ coll.map(&:label)
90
+
91
+ coll.doc # → [raw_hash, ...] (v2 compat)
92
+ coll.empty? # → Boolean
93
+ coll.length # → Integer
94
+
95
+ # Mutations — updates (existing fields)
96
+ field = coll.get_by_name('Summary')
97
+ field.value = 'New text'
98
+ coll.dirty_inputs # → [{ plainText: { id: 'f1', value: 'New text' } }]
99
+
100
+ # Mutations — additions (new field instances from buildFromTemplate)
101
+ coll.add(id: 'server_id', label: 'New Field', type: 'PlainText') { |f| f.value = 'X' }
102
+ coll.dirty_additions # → [{ plainText: { id: 'server_id', value: 'X' } }]
103
+
104
+ # Mutations — deletions (remove field instances)
105
+ coll.mark_for_deletion('f1') # by ID string
106
+ coll.mark_for_deletion(field_object) # by field object (uses field.id)
107
+ coll.dirty_deletions # → ['f1']
108
+
109
+ coll.dirty? # → true if any of the above have changes
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Setter API per Type
115
+
116
+ ### PlainText
117
+ ```ruby
118
+ field.value = 'text string'
119
+ field.as_input # → { plainText: { id:, value: } }
120
+ ```
121
+
122
+ ### RichText
123
+ ```ruby
124
+ field.content = '<p>HTML</p>'
125
+ field.value = '<p>HTML</p>' # alias
126
+ field.as_input # → { richText: { id:, content: } }
127
+ ```
128
+
129
+ ### Date
130
+ ```ruby
131
+ field.value = '2025-06-30T09:00:00+12:00' # ISO8601
132
+ field.as_input # → { date: { id:, value: } }
133
+ ```
134
+
135
+ ### Number / Gauge
136
+ ```ruby
137
+ field.value = 42.5
138
+ field.as_input # → { number: { id:, value: } } or { gauge: { id:, value: } }
139
+ ```
140
+
141
+ ### Select
142
+ ```ruby
143
+ field.select_option('Active') # select by name (deselects all others)
144
+ field.select_option('o1') # select by option id
145
+ field.select_option('opt1', 'opt2') # multi-select by id
146
+ field.clear_selection
147
+ field.selected_options # → [{ 'id' =>..., 'name' =>..., 'selected' => true }]
148
+ field.as_input # → { select: { id:, options: [{ id:, selected: }] } }
149
+ ```
150
+
151
+ ### Checklist
152
+ ```ruby
153
+ field.check('Step A') # check by label
154
+ field.check('i1', checked: false) # uncheck by id
155
+ field.as_input # → { checklist: { id:, items: [{ id:, checked: }] } }
156
+ ```
157
+
158
+ ### TagField (LocationField)
159
+ ```ruby
160
+ field.location_ids = ['loc1', 'loc2']
161
+ field.as_input # → { locationField: { id:, locationIds: [...] } }
162
+ ```
163
+
164
+ ### People
165
+ ```ruby
166
+ field.people_ids = ['person1', 'person2']
167
+ field.value = ['person1'] # alias
168
+ field.as_input # → { people: { id:, peopleIds: [...] } }
169
+ ```
170
+
171
+ ### Geo
172
+ ```ruby
173
+ field.address = '123 Main St, Auckland'
174
+ field.coordinates = { 'lat' => -36.86, 'lon' => 174.76 }
175
+ field.as_input # → { geo: { id:, address:, coordinates: } }
176
+ ```
177
+
178
+ ### ContractorEntities
179
+ ```ruby
180
+ field.contractor_entity_ids = ['ce1', 'ce2']
181
+ field.value = ['ce1'] # alias
182
+ field.as_input # → { contractorEntities: { id:, contractorEntityIds: [...] } }
183
+ ```
184
+
185
+ ### CrossReference
186
+ ```ruby
187
+ field.page_ids = ['page1', 'page2']
188
+ field.as_input # → { crossReference: { id:, referenceIds: [...] } }
189
+ ```
190
+
191
+ ### File / ImageGallery
192
+ ```ruby
193
+ # Requires prior file upload — see TODO "File upload"
194
+ field.file_container_ids = ['fc1', 'fc2']
195
+ field.as_input # → { file: { id:, fileContainerIds: [...] } }
196
+ # or { imageGallery: { id:, fileContainerIds: [...] } }
197
+ ```
198
+
199
+ ---
200
+
201
+ ## DataFieldOneToManyInput — Wiring to Mutations
202
+
203
+ `Input::Page::Update.from_model(page)` automatically collects field changes:
204
+
205
+ ```ruby
206
+ input = Input::Page::Update.from_model(page)
207
+ # input[:dataFields] may contain:
208
+ # {
209
+ # updates: [{ plainText: { id:, value: } }, ...], # dirty existing fields
210
+ # additions: [{ plainText: { id:, value: } }, ...], # newly added fields
211
+ # deletions: ['field_id_1', 'field_id_2'] # fields queued for deletion
212
+ # }
213
+ ```
214
+
215
+ `build_data_fields` only includes non-empty keys — if only `updates` exist, only
216
+ `updates` is present in the hash.
217
+
218
+ ---
219
+
220
+ ## Stage-Level Field Access
221
+
222
+ ```ruby
223
+ page = graphql.pages.get(page_id)
224
+ stage = page.stages['Risk Assessment']
225
+
226
+ # All fields in the stage (flat, across sections)
227
+ stage.components.get_by_name('Hazard Description').value = 'Exposure to...'
228
+
229
+ # Section-by-section
230
+ stage.sections.each do |section|
231
+ section.components.each { |f| puts "#{f.label}: #{f.value}" }
232
+ end
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Spec Coverage
238
+
239
+ | File | Coverage |
240
+ |---|---|
241
+ | `spec/ecoportal/api/graphql/base/page/data_field_spec.rb` | All 20 type dispatch, dirty tracking, PlainText, RichText, Select, People, Checklist, Collection (get_by_type/name, dirty_inputs, add/additions, deletions) |
242
+ | `spec/ecoportal/api/graphql/input/page/update_spec.rb` | from_model with dataFields.updates, additions, deletions |
@@ -0,0 +1,151 @@
1
+ # Code Spec: Dependency Map
2
+
3
+ **Scope:** All upstream and downstream gem dependencies for `ecoportal-api-graphql` — remote URLs, fork relationships, collaboration status, and access notes. Local paths are developer-specific; see the section below on local setup.
4
+ **Last updated:** 2026-06-02
5
+
6
+ ---
7
+
8
+ ## This Repo
9
+
10
+ | Property | Value |
11
+ |----------|-------|
12
+ | Gem name | `ecoportal-api-graphql` |
13
+ | Remote (origin) | https://gitlab.ecoportal.co.nz/oscar/ecoportal-api-graphql.git |
14
+ | Relationship | Target repo — all work happens here |
15
+
16
+ ---
17
+
18
+ ## Upstream Dependencies
19
+
20
+ ### ecoportal-api
21
+
22
+ | Property | Value |
23
+ |----------|-------|
24
+ | Gem name | `ecoportal-api` |
25
+ | Version constraint | `~> 0.10, >= 0.10.15` |
26
+ | Remote | https://gitlab.ecoportal.co.nz/ecoportal/ecoportal-api.git |
27
+ | Collaboration | **Team-managed** — we own this gem |
28
+ | Key deps | `dotenv`, `elastic-apm`, `http`, `rate_throttle_client` |
29
+ | Notes | Provides REST API base, authentication, org context, and the `Ecoportal::API::Common` namespace that this gem extends. |
30
+
31
+ ### ecoportal-api-v2
32
+
33
+ | Property | Value |
34
+ |----------|-------|
35
+ | Gem name | `ecoportal-api-v2` |
36
+ | Version constraint | `~> 3.3, >= 3.3.1` |
37
+ | Remote | https://gitlab.ecoportal.co.nz/oscar/ecoportal-api-v2.git |
38
+ | Collaboration | **Team-managed** — we own this gem |
39
+ | Key deps | `ecoportal-api`, `mime-types` |
40
+ | Notes | REST API v2 layer. Required by the entry point (`api-graphql.rb`). Depends on `ecoportal-api` itself. |
41
+
42
+ ### graphlient
43
+
44
+ | Property | Value |
45
+ |----------|-------|
46
+ | Gem name | `graphlient` |
47
+ | Version constraint | `>= 0.8.0, < 0.9` |
48
+ | Remote (our fork) | https://github.com/rellampec/graphlient.git |
49
+ | Remote (upstream) | https://github.com/ashkan18/graphlient.git |
50
+ | Collaboration | **Fork (active)** — we drive new features; upstream is `ashkan18/graphlient` |
51
+ | Git workflow | **Feature branches off our fork's working branch** (`feature/directives-dsl-support`). We open PRs upstream when appropriate but are not required to. Do NOT commit directly to `master`. |
52
+ | Key deps | `faraday ~> 2.0`, `graphql-client` |
53
+ | Notes | Friendly wrapper around `graphql-client`. `Common::GraphQL::Client` inherits from `Graphlient::Client`. |
54
+
55
+ ### graphql-client
56
+
57
+ | Property | Value |
58
+ |----------|-------|
59
+ | Gem name | `graphql-client` |
60
+ | Version constraint | (via graphlient) |
61
+ | Remote (our fork) | https://github.com/rellampec/graphql-client.git |
62
+ | Remote (upstream) | https://github.com/github-community-projects/graphql-client.git |
63
+ | Collaboration | **Fork (upstream-contribution only)** — we do NOT maintain this gem; upstream is `github-community-projects/graphql-client` |
64
+ | Git workflow | **ALWAYS use a feature branch** (e.g. `feature/allow-named-fragment-access`). Changes are intended as upstream PRs to `github-community-projects/graphql-client`. NEVER commit to `master`. If unsure about the branch name or PR strategy, ask the developer before committing. |
65
+ | Key deps | `activesupport >= 3.0`, `graphql >= 1.13.0` |
66
+ | Notes | Low-level GraphQL HTTP + parsing library. Pulled in transitively via `graphlient` — not a direct dependency. Check `Common::GraphQL::Patches` for any monkey-patches applied on top. |
67
+
68
+ ### graphql-ruby
69
+
70
+ | Property | Value |
71
+ |----------|-------|
72
+ | Gem name | `graphql` |
73
+ | Version constraint | `>= 1.13.0` (transitive, via graphql-client) |
74
+ | Remote | https://github.com/rmosolgo/graphql-ruby |
75
+ | Collaboration | **No collaboration** — external, read-only reference |
76
+ | Notes | Schema definition and execution engine. Used server-side by EcoPortal; client-side usage here is indirect. API reference: https://graphql-ruby.org |
77
+
78
+ ---
79
+
80
+ ## Downstream Dependencies
81
+
82
+ ### eco-helpers
83
+
84
+ | Property | Value |
85
+ |----------|-------|
86
+ | Gem name | `eco-helpers` |
87
+ | Requires this gem as | `ecoportal-api-graphql ~> 1.3, >= 1.3.4` |
88
+ | Remote | https://gitlab.ecoportal.co.nz/oscar/script_api_helpers.git |
89
+ | Collaboration | **Team-managed** — we own this gem |
90
+ | Notes | Primary consumer of this gem. **Backwards compatibility with `eco-helpers` must be preserved.** When a change could break it, cross-reference its usage before committing. |
91
+
92
+ ---
93
+
94
+ ## Dependency Chain Summary
95
+
96
+ ```
97
+ graphql-ruby (rmosolgo — external, read-only)
98
+
99
+ graphql-client (our fork ← github-community-projects/graphql-client)
100
+
101
+ graphlient (our fork ← ashkan18/graphlient)
102
+
103
+ ecoportal-api (team-managed — gitlab.ecoportal.co.nz)
104
+ ecoportal-api-v2 (team-managed — gitlab.ecoportal.co.nz)
105
+
106
+ ecoportal-api-graphql ← THIS REPO
107
+
108
+ eco-helpers (team-managed — gitlab.ecoportal.co.nz)
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Local Development Setup
114
+
115
+ Local paths are **developer-specific** — do not hardcode them in shared docs.
116
+
117
+ Each developer who works across multiple gems should clone the relevant repos and record their local paths in `.claude/local_paths.md` (git-ignored). See `.claude/local_paths.example.md` for the format.
118
+
119
+ ### Using local gem overrides with Bundler
120
+
121
+ To point Bundler at a local clone instead of the published gem:
122
+
123
+ ```bash
124
+ bundle config local.ecoportal-api /your/path/to/ecoportal-api
125
+ bundle config local.ecoportal-api-v2 /your/path/to/ecoportal-api-v2
126
+ bundle config local.graphlient /your/path/to/graphlient
127
+ ```
128
+
129
+ Or add a `path:` entry in `Gemfile` during development (remove before committing):
130
+
131
+ ```ruby
132
+ gem 'graphlient', path: '/your/path/to/graphlient'
133
+ ```
134
+
135
+ ### For AI agents working with local clones
136
+
137
+ 1. Check `.ai-assistance/local_paths.md` for this developer's local paths.
138
+ 2. If the file doesn't exist, the repos are not locally available — use the remote URLs for reference only (read via web fetch or ask the developer to clone).
139
+ 3. Never assume a specific local path exists — always check first.
140
+
141
+ ---
142
+
143
+ ## Upstream Change Decision Guide
144
+
145
+ If a task requires changes in an upstream gem, the options are (confirm with developer before proceeding):
146
+
147
+ 1. **Local monkey-patch** — apply the fix from within `ecoportal-api-graphql` (see `Common::GraphQL::Patches`). Fast, contained, but temporary debt.
148
+ 2. **Fork change** — commit to our fork (for `graphlient` or `graphql-client`). Appropriate for lasting fixes we own.
149
+ 3. **New version** — for `ecoportal-api` or `ecoportal-api-v2` (team-owned), cut a new version. For external forks, optionally contribute back upstream.
150
+
151
+ Document the decision in the active project's `DECISIONS.md`.
@@ -0,0 +1,234 @@
1
+ # Code Spec: `as_update` / `as_input` Pipeline
2
+
3
+ **Scope:** `Common::GraphQL::Model::Diffable`, `Common::GraphQL::Model::AsInput`,
4
+ `DiffService`, `ClassicDiffService`, `HashDiffNesting`
5
+ **Last updated:** 2026-06-05
6
+
7
+ ---
8
+
9
+ ## What This Pipeline Does
10
+
11
+ Models that include `AsInput` (via `Model < DoubleModel`) can:
12
+ 1. **Diff themselves** against their original document via `#as_update` → returns a Hash
13
+ of only the changed properties.
14
+ 2. **Convert that diff** to mutation input via `#as_input` → returns a Hash ready to be
15
+ passed as the `input:` variable to a GraphQL mutation.
16
+
17
+ ---
18
+
19
+ ## Class Hierarchy
20
+
21
+ ```
22
+ Common::GraphQL::Model < Common::Content::DoubleModel
23
+ includes Diffable → provides #as_update, #dirty?
24
+ includes AsInput → provides #as_input, .as_input(hash)
25
+
26
+ Diffable::DiffService < ClassicDiffService
27
+ includes HashDiffNesting → change_diff, patch_update, patch_new, patch_delete, ...
28
+ includes HashHelpers
29
+
30
+ Diffable::ClassicDiffService
31
+ includes HashDiffNesting
32
+ provides #diff, #classic_diff, #curr_doc, #prev_doc
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Current Output Format of `DiffService#diff` — KNOWN ISSUE
38
+
39
+ **`#as_update` calls `DiffService#diff` which calls `classic_diff(flat: true)`, which calls
40
+ `change_diff(curr_doc, prev_doc)`.**
41
+
42
+ `change_diff` routes through `patch_update` for any Hash that has an `:id` key, producing:
43
+
44
+ ```ruby
45
+ {
46
+ id: "abc123",
47
+ api_operation: :update,
48
+ change_data: { name: "New Name", status: "active" }
49
+ }
50
+ ```
51
+
52
+ **This is WRONG for GraphQL mutations.** The GraphQL mutation input should be flat:
53
+
54
+ ```ruby
55
+ { id: "abc123", name: "New Name", status: "active" }
56
+ ```
57
+
58
+ **Why it's wrong:** GraphQL mutation inputs (`UpdateActionInput`, `UpdatePageInput`, etc.)
59
+ do not have `api_operation` or `change_data` fields — they expect changed properties directly
60
+ at the top level alongside `id`.
61
+
62
+ **Root cause:** `HashDiffNesting` was ported from `ecoportal-api-v2`'s `HashDiffPatch`.
63
+ The v2 gem uses JSON Patch semantics (`api_operation`, `change_data`). GraphQL mutations
64
+ do not — they use direct property assignment.
65
+
66
+ **Decision (2026-06-04):** GraphQL `as_update` must produce changed-props-only output with
67
+ no patch-operation wrapper. See `DECISIONS.md` ("as_update diff: changed props only").
68
+
69
+ **Fix required (TODO 4.2/4.3):** Either:
70
+ - Override `classic_diff` in `DiffService` to call `change_data(curr, prev)` directly
71
+ (returns just the changed fields, no wrapper), and re-add `id` separately; or
72
+ - Have `AsInput#as_input` detect and unwrap the patch-op structure before building input.
73
+ The preferred approach per the DECISIONS is to fix `DiffService` itself.
74
+
75
+ ### Nested objects
76
+
77
+ `diff_reduce` cascades through nested model objects (those that respond to `#as_update`)
78
+ and replaces the nested path in the result with each nested model's own `as_update` output.
79
+ These nested diffs also currently produce `api_operation`/`change_data` wrappers if the
80
+ nested object has an `id`.
81
+
82
+ For GraphQL, nested arrays of objects with IDs (e.g. `locations`, `associatedPeople`)
83
+ are expressed differently per mutation — sometimes as `[ID]` arrays, sometimes as
84
+ `IdDiffInput` (add/remove deltas), sometimes as full nested input objects. The `InputClass`
85
+ (per the `from_model` pattern, TODO 4.3) is responsible for reshaping nested diffs into
86
+ the correct format for each mutation.
87
+
88
+ ---
89
+
90
+ ## `AsInput` Current Behavior
91
+
92
+ ### Instance method `#as_input(clientMutationId: '')`
93
+
94
+ 1. Calls `as_update` → gets the diff hash (currently with patch-op wrapper, see above).
95
+ 2. Calls `self.class.as_input(diff, clientMutationId: ...)`.
96
+
97
+ ### Class method `.as_input(hash, clientMutationId: '')`
98
+
99
+ 1. Deep-converts hash keys to symbols.
100
+ 2. Merges `clientMutationId:`.
101
+ 3. Calls `remove_nil_keys_deep(hash, target: :id)` — removes nil-valued `:id` keys
102
+ (keeps `id:` only when it has a value; removes nil ids from nested objects so they
103
+ don't confuse the server).
104
+
105
+ **Current output (wrong):**
106
+ ```ruby
107
+ {
108
+ id: "abc123",
109
+ api_operation: :update,
110
+ change_data: { name: "New Name" },
111
+ clientMutationId: ""
112
+ }
113
+ ```
114
+
115
+ **Desired output (target):**
116
+ ```ruby
117
+ { id: "abc123", name: "New Name", clientMutationId: "" }
118
+ ```
119
+
120
+ ---
121
+
122
+ ## `patch_ver` — Concurrency Control
123
+
124
+ **Schema:** `patchVer: Int!` (non-null) on `BasePageInterface`. Present on all Page types.
125
+ `BasePage` in the gem maps it as `passthrough :patchVer`.
126
+
127
+ **How it works:**
128
+ - Every Page response includes `patchVer` (an incrementing Integer).
129
+ - On `UpdatePage` mutation, the caller may pass `patchVer:` (optional).
130
+ - The server checks that the submitted `patchVer` matches the current value; if not,
131
+ the update is rejected (optimistic locking / last-write-wins prevention).
132
+
133
+ **Current gap (TODO 4.2):** `AsInput#as_input` does not inject `patchVer` into the
134
+ output. The subject model (`BasePage` subclass) holds `patchVer` via `passthrough :patchVer`
135
+ but it is never extracted and included in the mutation input hash.
136
+
137
+ **Fix required:** In `AsInput#as_input` (instance method), read `self.patchVer` if the
138
+ model responds to it, and include it in the input hash at the top level:
139
+
140
+ ```ruby
141
+ def as_input(clientMutationId: '')
142
+ diff = as_update # TODO: once fixed, returns flat changed props
143
+ diff[:patchVer] = patchVer if respond_to?(:patchVer) && !patchVer.nil?
144
+ self.class.as_input(diff, clientMutationId: clientMutationId)
145
+ end
146
+ ```
147
+
148
+ ---
149
+
150
+ ## `from_model` Pattern (TODO 4.3)
151
+
152
+ **Decision (2026-06-04):** Conversion from model diff to mutation-specific input shape is
153
+ owned by the `Input` class, not the model. Each mutation's input class implements
154
+ `InputClass.from_model(model)`.
155
+
156
+ **Why:** The model (`BasePage`, `Base::Action`, etc.) should not know which mutation it
157
+ feeds. The input class knows its own schema shape (field names, nested structure, ID vs
158
+ IdDiff arrays, etc.).
159
+
160
+ **Pattern:**
161
+ ```ruby
162
+ class Input::Action::Update < Logic::Input
163
+ def self.from_model(action_model)
164
+ diff = action_model.as_update # after fixing: flat {name:, status:, ...}
165
+ new(
166
+ id: action_model.id,
167
+ patchVer: action_model.patchVer, # if applicable
168
+ **map_fields(diff)
169
+ )
170
+ end
171
+
172
+ private_class_method def self.map_fields(diff)
173
+ # rename, reshape, filter fields as needed for this specific mutation input
174
+ diff.slice(:name, :description, :status, :dueDate, :assignedPersonMemberIds, ...)
175
+ end
176
+ end
177
+ ```
178
+
179
+ **The `as_input` instance method becomes a convenience delegator:**
180
+ ```ruby
181
+ def as_input(target_class: default_input_class, **kargs)
182
+ target_class.from_model(self, **kargs)
183
+ end
184
+ ```
185
+
186
+ ---
187
+
188
+ ## `root?` Objects in the Diff
189
+
190
+ Models marked `root!` (via `Ecoportal::API::Common::GraphQL::ClassHelpers`) are excluded
191
+ from cascaded diffs. These are typically "lookup" objects that are referenced but not
192
+ owned — e.g. `Base::Organization` (root!), `Base::Action` (root!). Their nested presence
193
+ in a model's JSON will not generate diff entries.
194
+
195
+ This is correct behaviour: you don't diff a referenced Organization when updating a Page;
196
+ you only diff properties the Page itself owns.
197
+
198
+ ---
199
+
200
+ ## Key Files
201
+
202
+ | File | Role |
203
+ |------|------|
204
+ | `lib/.../model.rb` | `Model < DoubleModel`, includes `Diffable` and `AsInput` |
205
+ | `lib/.../model/diffable.rb` | `#as_update`, `#dirty?`, `DIFF_CLASS` |
206
+ | `lib/.../model/diffable/diff_service.rb` | Main diff logic, `#diff`, `#diff_reduce` |
207
+ | `lib/.../model/diffable/classic_diff_service.rb` | Base: `#curr_doc`, `#prev_doc`, `change_diff` dispatch |
208
+ | `lib/.../model/diffable/hash_diff_nesting.rb` | `change_diff`, `patch_update/new/delete`, array diffing |
209
+ | `lib/.../model/as_input.rb` | `#as_input`, `.as_input(hash)` class method |
210
+ | `lib/.../interface/base_page.rb` | `passthrough :patchVer` — Page models carry this |
211
+
212
+ ---
213
+
214
+ ## Backend Reference (ecoPortal_master)
215
+
216
+ - `app/graphql/types/pages/interfaces/base_page_interface.rb` — `patchVer: Int!` (non-null)
217
+ - `app/graphql/mutations/pages/update.rb` — `argument :patch_ver, Integer, required: false`
218
+ - `app/graphql/types/fill_in_page/inputs/page_input.rb` — Page update input fields:
219
+ `locations`, `other_tags`, `time_zone`, `state`, `task_priority`, `name`, `external_id`
220
+ - `app/graphql/types/fill_in_page/inputs/data_field_input.rb` — DataField inputs (complex)
221
+
222
+ **UpdatePage mutation shape:**
223
+ ```graphql
224
+ mutation UpdatePage($id: ID!, $patch_ver: Int, $page: PageInput, $data_fields: [DataFieldInput], ...) {
225
+ updatePage(id: $id, patchVer: $patch_ver, page: $page, dataFields: $data_fields, ...) {
226
+ item { ...BasePageInterface }
227
+ errors { ... }
228
+ }
229
+ }
230
+ ```
231
+
232
+ Note: `id`, `patchVer`, and `page` (the input object) are separate top-level arguments —
233
+ not nested under a single `input:` object like Action mutations. This is a structural
234
+ difference that the `from_model` pattern must account for per mutation type.