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,245 @@
1
+ # Decisions — Page Model Project
2
+
3
+ ---
4
+
5
+ ## 2026-06-07 — Two search modalities: org search vs register search
6
+
7
+ **Context:** Scripts that need to find pages before updating them have two query paths,
8
+ with fundamentally different data sources, performance profiles, and coverage.
9
+
10
+ ### Org search — `currentOrganization { pages(...) }`
11
+
12
+ - **Source:** PostgreSQL DB, rendered by the API layer
13
+ - **Returns:** `PageUnion` (`BasicPage | PhasedPage`) — the same model returned by
14
+ mutations, fully populated from the DB
15
+ - **Performance:** Slow — approximately 3 seconds per page on render due to API-layer
16
+ data assembly. **Implication:** avoid re-fetching a page after update if possible;
17
+ the mutation payload already returns the updated `PageUnion`.
18
+ - **Data completeness:** Full — includes `patchVer`, all field values, data field IDs
19
+ (server-assigned after DB creation), and the full type structure needed for mutations.
20
+ - **Archived pages:** Supported via a query argument — can search archived pages.
21
+ - **Best for:** Pre-update fetch (need `patchVer` + field IDs), post-build-from-template
22
+ fetch (need server-assigned field IDs to construct create input).
23
+
24
+ ### Register search — `currentOrganization { register(id:) { previewPages(...) } }`
25
+
26
+ **Correction 2026-06-07 (verified from Insomnia production queries):** The field is
27
+ `previewPages`, not `pages`. Archived pages ARE supported via `includeArchived: Boolean`
28
+ parameter (defaults to `false`).
29
+
30
+ - **Source:** Elasticsearch (ES) — the org's search index, not the DB
31
+ - **Returns:** `PreviewPage` — field preview values from ES snapshots. No field IDs, no
32
+ `patchVer`, no typed data field models. Insufficient for building mutation inputs.
33
+ - **Performance:** Fast — sub-second, ES-powered.
34
+ - **Data completeness:** Partial — preview values only. Use to find page IDs, then switch
35
+ to org search for full data.
36
+ - **Archived pages:** Supported via `includeArchived: Boolean` parameter.
37
+ - **Extra args:** `presetViewId: ID` — applies a register PageView preset configuration.
38
+ - **Best for:** Fast lookup by metadata (`externalId`, state, location tags, date ranges)
39
+ to get page IDs before switching to org search for full data fetch.
40
+
41
+ ### Search config — `SearchConf` (Hash type)
42
+
43
+ The `searchConf` argument is not a typed GraphQL model — it is passed as a `Hash`
44
+ (the schema shows it as `Search`). Engineering has backend classes for it but these are
45
+ not exposed as GraphQL input types.
46
+
47
+ **Verified structure (from Insomnia):**
48
+
49
+ ```json
50
+ {
51
+ "filters": [ <filter_object>, ... ],
52
+ "sorters": { "key": "created_at", "direction": "asc" },
53
+ "query": "optional full-text string"
54
+ }
55
+ ```
56
+
57
+ **Filter operations (snake_case strings):**
58
+ - `exact_filter` — equality: `{ key: "external_id", value: "X" }`
59
+ - `date_filter` — date range: `{ key: "updated_at", gte: "ISO8601", time_zone: "UTC" }`
60
+ - `register_filter` — scope to registers (org search): `{ ids: ["id1"] }`
61
+ - `and_filter` — explicit AND group: `{ filters: [...] }`
62
+ - `or_filter` — OR group: `{ filters: [...] }`
63
+
64
+ **Sorter:** `{ "key": "field_name", "direction": "asc"|"desc" }` — note key field is
65
+ `key`, not `fieldName`. Can be a single object or an array.
66
+
67
+ Full reference with all production examples: `.ai-assistance/code/search_filters.md`
68
+
69
+ **Reference:** Search filter docs partially exist in Confluence APIDOCS (most common
70
+ filters). Full coverage is pending — see the Search Filter Builder project.
71
+
72
+ ---
73
+
74
+ ## 2026-06-07 — CommonPageUnion: unified fragment for all page integration patterns
75
+
76
+ **Context:** Customer integration scripts share three fundamental page interaction patterns,
77
+ all of which require reading the full page content (including data field IDs and structure):
78
+
79
+ 1. **Build from template** — After calling `buildFromTemplate`, the server returns a built
80
+ draft whose data field IDs are already assigned by the DB. Scripts must fetch this draft
81
+ via `CommonPageUnion` to retrieve those IDs before they can construct the `dataFields`
82
+ input for the subsequent `createFromTemplate` call.
83
+
84
+ 2. **Fetch before update** — Before calling `updatePage`, scripts must fetch the current page
85
+ via `CommonPageUnion` to get the current `patchVer` (required for optimistic concurrency)
86
+ AND the current data field IDs and values. Field IDs change after DB creation and cannot
87
+ be assumed — they must be read from the live page.
88
+
89
+ 3. **Fetch after create/update** — To confirm the result, scripts re-fetch via
90
+ `CommonPageUnion` and inspect the returned state.
91
+
92
+ **Decision:** `Fragment::Pages::CommonPageUnion` is a single, parameterised fragment that
93
+ covers all three patterns. Rather than having separate lightweight/heavyweight variants,
94
+ three boolean query variables control how much is fetched in a given call:
95
+
96
+ | Variable | Purpose | Use in pattern |
97
+ |---|---|---|
98
+ | `$content` | Include field values, stage completions, people, files | (2) update, (3) verify |
99
+ | `$only_content` | Exclude structural/config fields — content values only | (3) verify result |
100
+ | `$fields` | Include data field definitions (labels, options, structure) | (1) build, (2) update |
101
+
102
+ **Design rationale — educating customer technicians:** The two-step sequences for create
103
+ and update were deliberately consolidated under one fragment to give customer technicians
104
+ a clear, documented path into the GraphQL integration:
105
+
106
+ - **CREATE sequence:** (a) `buildFromTemplate` → fetch built draft with `CommonPageUnion`
107
+ (`$fields: true`) to get data field IDs; (b) fill in values and call `createFromTemplate`.
108
+ - **UPDATE sequence:** (a) fetch current page with `CommonPageUnion` (`$content: true,
109
+ $fields: true`) to get `patchVer` + current field IDs and values; (b) build update
110
+ operations and call `updatePage`.
111
+
112
+ GraphQL's declarative nature (clients specify what they need) is new to many technicians.
113
+ The directive variables make it visible and learnable — they can start with
114
+ `$content: true, $fields: true` to get everything, then dial back once they understand
115
+ what each variable controls.
116
+
117
+ **Note on Build - Model (legacy pattern):** The Insomnia export contains an older
118
+ `Build - Model` request with its own inline fragment definitions (no `CommonPageUnion`).
119
+ This predates the unified fragment approach. It was used before the team settled on a
120
+ single documented pattern for customer support knowledge sharing. Treat it as legacy;
121
+ all new scripts should use the `Build - Standard` / `CommonPageUnion` approach.
122
+
123
+ **Note on compound mutations:** The `ArchivePage` Insomnia request runs two mutations in
124
+ one request — `updatePage` (blanks `externalId`) then `archivePage`. This is valid GraphQL
125
+ (multiple operations in one definition, executed in declaration order by the server). It was
126
+ designed for a customer whose middleware was stateless and could not sequence two separate HTTP
127
+ requests within a session. The `operationName` field in the JSON payload identifies the
128
+ primary named definition. This pattern is available to any integration that needs atomic
129
+ sequencing without server-side transactions.
130
+
131
+ **Reference:** Documented in Jira API Docs space (Confluence APIDOCS).
132
+ Fragment lives at: `lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb`
133
+ Accessed via: `fragment.pages.assemble(:CommonPageUnion)` or via `assemble_fragments`
134
+ when `...CommonPageUnion` appears in a query string.
135
+
136
+ ---
137
+
138
+ ## 2026-06-05 — Page model is phased; DataFields deferred to Phase E
139
+
140
+ **Context:** Pages are the central EcoPortal entity with 50+ fields, two concrete types
141
+ (BasicPage / PhasedPage), stages, sections, 20 data field types, AI features, and
142
+ workflow. Attempting to implement everything at once is impractical.
143
+
144
+ **Decision:** Phase the work. Phase A (read) and Phase B (write) cover the core lifecycle
145
+ that customer scripts need. DataFields (Phase E) deferred — they require domain knowledge
146
+ of field types and are unlikely to be needed for structural automation scripts.
147
+
148
+ **Consequences:** Phase A/B output will not include `dataFields` in mutation inputs.
149
+ Scripts that need to update field values must wait for Phase E.
150
+
151
+ ---
152
+
153
+ ## 2026-06-05 — Page creation requires a template; no blank page mutation exists
154
+
155
+ **Context:** The schema has no `createPage` mutation. All creation goes via
156
+ `createPageFromTemplate(input: CreateFromTemplateInput!)` or `buildPageFromTemplate`.
157
+ `CreateFromTemplateInput` takes `templateId: ID!` and optionally `dataFields`.
158
+
159
+ **Decision:** Phase D (creation) is scoped to template-based creation only. No attempt to
160
+ expose a blank-page creation API because none exists on the server.
161
+
162
+ **Consequences:** Scripts that create pages must know the target template ID. The gem
163
+ should provide a way to look up templates (`Query::Templates`).
164
+
165
+ ---
166
+
167
+ ## 2026-06-05 — Both BasicPage and PhasedPage mapped; Stage deferred to Phase C
168
+
169
+ **Context:** `PageUnion = BasicPage | PhasedPage`. PhasedPage adds stages; Stage has its
170
+ own `dataFields`, `tasks`, `sections`. Scripting typically works at the Page level (not
171
+ individual stage fields).
172
+
173
+ **Decision:** Phase A maps `BasicPage` and `PhasedPage` as concrete model classes
174
+ inheriting from `Interface::BasePage`. Stage model populated in Phase C. Scripts can
175
+ access stages via `page.stages` once Phase C is done.
176
+
177
+ **Consequences:** `PhasedPage#stages` returns raw data (passarray) until Phase C adds a
178
+ proper `Stage` model class.
179
+
180
+ ---
181
+
182
+ ## 2026-06-05 — `patchVer` always queried and always passed on update
183
+
184
+ **Context:** `patchVer: Int!` is non-null on every page read. The `updatePage` mutation
185
+ accepts it optionally. The server uses it for optimistic concurrency control.
186
+
187
+ **Decision:** The Page fragment always includes `patchVer`. The `UpdatePage` mutation
188
+ input always includes `patchVer` sourced from the model. This is not optional in our gem
189
+ even though it is optional in the schema.
190
+
191
+ **Reason:** Silent concurrency failures (last-write-wins without warning) are worse than
192
+ an occasional stale-version rejection. Making `patchVer` mandatory in our usage prevents
193
+ data loss from concurrent edits.
194
+
195
+ **Consequences:** Any code that builds `UpdatePageInput` must have previously fetched the
196
+ page to get the current `patchVer`. You cannot update a page you haven't read first.
197
+
198
+ ---
199
+
200
+ ## 2026-06-05 — `stageId` in UpdatePageInput: Phase C concern
201
+
202
+ **Context:** `UpdatePageInput` has `stageId: ID` which targets a specific stage on a
203
+ phased page. Stage-specific operations (submit, review, complete) also use this.
204
+
205
+ **Decision:** Phase B implements page-level updates only (name, state, locations, tags,
206
+ `patchVer`). Stage-targeting (`stageId`, `submit`, `task` input) is Phase C.
207
+
208
+ ---
209
+
210
+ ## 2026-06-06 — PageUnion implemented as a factory module, not a class
211
+
212
+ **Context:** The GraphQL `page` and `pages` fields return `PageUnion = BasicPage | PhasedPage`.
213
+ The response wrapping layer calls `response_class.new(data)` and connection node instantiation
214
+ calls `node_class.new(item, parent: self)`. Both require a single "class" that can dispatch to
215
+ the correct concrete type based on `__typename`.
216
+
217
+ **Decision:** `Model::PageUnion` is implemented as a Ruby module with a custom `.new` method
218
+ that inspects `doc['__typename']` and delegates to `Model::Page::Basic`, `Model::Page::Phased`,
219
+ or `Interface::BasePage` (fallback). It is used as both `item_class` on `Query::Page` and
220
+ `node_class` on `Connection::Page`.
221
+
222
+ `class_resolver` accepts modules since `resolve_class` uses `Kernel.const_get` for strings and
223
+ returns the constant as-is. The `.new` method passes through `**opts` so `parent:` and `key:`
224
+ from `embeds_many` reach the concrete constructor correctly.
225
+
226
+ **Consequences:** Callers always receive a concrete `Page::Basic` or `Page::Phased` instance —
227
+ `PageUnion` itself is never instantiated. The `__typename` field must be included in the
228
+ `PageFields` fragment for dispatch to work.
229
+
230
+ ---
231
+
232
+ ## 2026-06-05 — Fragment::Page initial scope
233
+
234
+ **Context:** `BasePageInterface` has 50 fields. Most are read-only context or authorization
235
+ flags. Customer scripts primarily need identity, state, location, and versioning fields.
236
+
237
+ **Decision (initial fragment scope):**
238
+ - **Always include:** `id`, `name`, `uid`, `icon`, `patchVer`, `state`, `archived`,
239
+ `archivedAt`, `draft`, `externalId`, `timeZone`, `otherTags`, `taskPriority`,
240
+ `sourceTemplateId`, `createdAt`, `updatedAt`, `creatorName`
241
+ - **Locations:** `locations { id name }` (via spread of `LocationNode` fragment)
242
+ - **Exclude initially:** `dataFields`, `sections`, `workflow`, `forces`, `tasks`,
243
+ `comments`, AI fields, authorization `can*` fields
244
+
245
+ Fields can be added to the fragment as needed by downstream scripts.
@@ -0,0 +1,190 @@
1
+ # TODOs — Page Model Project
2
+
3
+ > Created 2026-06-05 from live schema introspection.
4
+ > Natural implementation order: A → B → C → D → E
5
+ > Phases C, D, E can be deferred — Phase A + B covers the core script use case.
6
+
7
+ ---
8
+
9
+ ## Phase A — Read (queries, fragment, models)
10
+
11
+ - [x] **A.1** Fragment::PageFields on BasePageInterface — all initial-scope fields + `...LocationNode`.
12
+ *Done 2026-06-07 — `lib/ecoportal/api/graphql/fragment/page.rb`*
13
+
14
+ - [x] **A.2** Interface::BasePage: added `passboolean :draft`, `passthrough :externalId`.
15
+ *Done 2026-06-07 — archivedAt was already present*
16
+
17
+ - [x] **A.3** `Base::Page::Basic < Interface::BasePage` (new); `Base::Page::Phased`: added `passarray :stages`.
18
+ *Done 2026-06-07 — naming follows existing nesting: Base::Page::Basic, Base::Page::Phased*
19
+
20
+ - [x] **A.4** `Model::Page::Basic < Base::Page::Basic` (new); `Model::Page::Phased` already existed with resolvers.
21
+ *Done 2026-06-07*
22
+
23
+ - [x] **A.5** `Query::Page` (single by id); `Model::PageUnion` factory dispatches on `__typename`.
24
+ *Done 2026-06-07 — PageUnion approach documented in DECISIONS.md*
25
+
26
+ - [x] **A.6** `Query::Pages` — paginated, `Connection::Page` with `PageUnion` node_class.
27
+ *Done 2026-06-07*
28
+
29
+ - [x] **A.7** `Model::Organization`: wired `query :page`, `query :pages`, `query :templates`.
30
+ *Done 2026-06-07*
31
+
32
+ - [x] **A.8** `Query::Templates` — reuses `Connection::Page` and `PageFields` fragment.
33
+ *Done 2026-06-07*
34
+
35
+ - [x] **A.9** RSpec specs for `Query::Page`, `Query::Pages`, fragment assembly.
36
+ `spec/ecoportal/api/graphql/query/page_spec.rb` — 7 examples (field_name, accepted_params,
37
+ item_class, PageFields/LocationNode/BasicPageFields/PhasedPageFields fragment assembly).
38
+ `spec/ecoportal/api/graphql/query/pages_spec.rb` — 7 examples (field_name, accepted_params,
39
+ item_class, connection_class, fragment assembly, SearchConf acceptance).
40
+ *Done 2026-06-07 — 14 examples, 0 failures*
41
+
42
+ ---
43
+
44
+ ## Phase B — Write (update mutation)
45
+
46
+ - [x] **B.1** `Input::Page` — `name/state/taskPriority/timeZone/externalId/locations/otherTags`.
47
+ *Done 2026-06-07 — `lib/ecoportal/api/graphql/input/page.rb`*
48
+
49
+ - [x] **B.2** `Input::Page::Update.from_model` — nested `page:` sub-hash, always injects `patchVer`, supports `publish:` flag.
50
+ *Done 2026-06-07 — `lib/ecoportal/api/graphql/input/page/update.rb`*
51
+
52
+ - [x] **B.3** `Mutation::Page::Update` + `Payload::Page::Update` (item via PageUnion).
53
+ *Done 2026-06-07*
54
+
55
+ - [x] **B.4** `Mutation::Page::Archive` + `Mutation::Page::Unarchive` + corresponding payloads + inputs.
56
+ *Done 2026-06-07*
57
+
58
+ - [x] **B.5** `Builder::Page`: `update/archive/unarchive` methods.
59
+ *Done 2026-06-07*
60
+
61
+ - [x] **B.6** `Ecoportal::API::GraphQL#page` → `Builder::Page`.
62
+ *Done 2026-06-07*
63
+
64
+ - [x] **B.7** RSpec specs: `Input::Page::Update.from_model` (no-change nil, patchVer, publish, clientMutationId); `Model::PageUnion` __typename dispatch.
65
+ *Done 2026-06-07 — archive/unarchive input specs deferred (low value, straightforward passthrough)*
66
+
67
+ ---
68
+
69
+ ## Phase C — PhasedPage stages
70
+
71
+ > Prerequisite: Phase A model classes exist.
72
+
73
+ - [x] **C.1** Populate `Base::Page::Phased::Stage` with fields from `Stage` type:
74
+ `id`, `name`, `ordering`, `state`, `active`, `started`; predicate methods `active?`/`started?`;
75
+ `sections` → SectionCollection, `components` → DataField::Collection, `[]` hash-style access.
76
+ *Done 2026-06-07 — 17 specs in `spec/ecoportal/api/graphql/base/page/phased/stage_spec.rb`*
77
+
78
+ - [x] **C.2** Add `embeds_many :stages, klass: Stage` on `Base::Page::Phased`.
79
+ *Done 2026-06-07 — `Compat::StageCollection#coerce_stage` wraps raw hashes in `Stage`*
80
+
81
+ - [x] **C.3** Add `stageId` support to `Input::Page::Update` and `Mutation::Page::Update`
82
+ for stage-targeted updates.
83
+ *Done 2026-06-07 (commit 6305f69) — stageId + submit kwargs; non-nil result for stage-only submit*
84
+
85
+ - [x] **C.4** Add submit/review task mutations: `approveReviewPageTask`, `rejectReviewPageTask`,
86
+ `batchUpdateReviewPageTask`, `restartReviewPageTask`, `undoReviewPageTask`.
87
+ `Input::Page::ReviewTask` (id, stageId, comment); `Payload::Page::ReviewTask` (item: PageUnion);
88
+ 5 mutation classes; `Builder::Page` wired with `approve/reject/restart/undo/batch_update_review_task`.
89
+ *Done 2026-06-07 — 5 specs in `spec/ecoportal/api/graphql/input/page/review_task_spec.rb`*
90
+
91
+ ---
92
+
93
+ ## Phase D — Creation from template
94
+
95
+ - [x] **D.1** `Input::Page::CreateFromTemplate` — `templateId`, `showHiddenData`, `stageId`,
96
+ `submit`, `attach`, `draft`, `clientMutationId`, `dataFields` (raw passthrough), `page` (embeds_one).
97
+ *Pre-existing*
98
+
99
+ - [x] **D.2** `Mutation::Page::CreateFromTemplate` — `createPageFromTemplate`, `CreateFromTemplateInput!`.
100
+ *Pre-existing*
101
+
102
+ - [x] **D.3** `Mutation::Page::BuildFromTemplate` — `buildPageFromTemplate`, `BuildFromTemplateInput!`.
103
+ *Pre-existing*
104
+
105
+ - [x] **D.4** Draft lifecycle mutations — all share `Payload::Page::Draft` (item: PageUnion):
106
+ - `Input::Page::CreateDraft` (templateId), `Mutation::Page::CreateDraft` (createPageDraft)
107
+ - `Input::Page::DeleteDraft` (id), `Mutation::Page::DeleteDraft` (deleteDraftPage)
108
+ - `Input::Page::PublishDraft` (id), `Mutation::Page::PublishDraft` (publishDraftPage)
109
+ *Done 2026-06-07 — 4 specs in `spec/ecoportal/api/graphql/input/page/create_draft_spec.rb`*
110
+
111
+ - [x] **D.5** `Builder::Page` wired for all template + draft operations:
112
+ `build_from_template`, `create_from_template`, `create_draft`, `delete_draft`, `publish_draft`.
113
+ *Done 2026-06-07*
114
+
115
+ ---
116
+
117
+ ## Phase E — DataFields (deferred — complex)
118
+
119
+ > DataFields are the rich content of pages (form fields). 20 field types.
120
+ > Defer until Phases A–D are stable and there is a clear script use case.
121
+
122
+ - [x] **E.1** All 20 DataField types implemented in `lib/ecoportal/api/graphql/base/page/data_field/`.
123
+ 10 writable types (PlainText, RichText, Date→DateField, Number, Gauge, Select, Checklist,
124
+ TagField, People, Geo, ContractorEntities, CrossReference, File→FileField, ImageGallery).
125
+ 6 read-only stubs (Signature, Mailbox, ActionsList, Law, AiSummary, Table).
126
+ TYPE_MAP covers all 20 `__typename` → Ruby class mappings.
127
+ *Done 2026-06-08 — 68 examples in data_field_spec.rb*
128
+
129
+ - [x] **E.2** `DataFieldOneToManyInput { additions, updates, deletions }` fully wired:
130
+ `Collection#mark_for_deletion` + `#dirty_deletions`; `DataFieldAccess#data_fields_deletions`;
131
+ `Input::Page::Update.from_model` includes deletions in `build_data_fields`.
132
+ *Done 2026-06-08*
133
+
134
+ - [x] **E.3** Data field access wired on all page models via `Concerns::DataFieldAccess`
135
+ included in `Interface::BasePage` (`components`, `field_collection`, `data_fields_updates`,
136
+ `data_fields_additions`, `data_fields_deletions`). Stage exposes `components` via sections.
137
+ *Pre-existing + additions from E.2*
138
+
139
+ - [x] **E.4** Code-spec doc: `.ai-assistance/code/data_fields.md` — all 20 types, setter API,
140
+ Collection API, DataFieldOneToManyInput wiring, stage-level access, spec coverage table.
141
+ *Done 2026-06-08*
142
+
143
+ ---
144
+
145
+ ## Cross-cutting
146
+
147
+ - [x] **X.1** `Fragment::BasicPageFields on BasicPage` — sections (id, heading, __typename).
148
+ *Done 2026-06-07 — fragment/page.rb*
149
+
150
+ - [x] **X.2** `Fragment::PhasedPageFields on PhasedPage` — stagesIndex, currentStage, activeStages.
151
+ *Done 2026-06-07 — fragment/page.rb*
152
+
153
+ - [x] **X.3** `Query::PageDelta` — `pageDelta(deltaInput: [DeltaInput!]!, searchConf: Search)`.
154
+ Actual schema: takes `{ id, deltaAt: ISO8601DateTime! }` per page, not just ids.
155
+ Returns `[DeltaResult]` (UPDATED/NOT_FOUND/NEW). `Base::DeltaResult`, `Input::DeltaInput`.
156
+ *Done 2026-06-07*
157
+ - [x] **X.4** CHANGELOG and version bump — done 2026-06-07, bumped to 1.4.0.
158
+ - [x] **X.5** Cycle-end Gemini review — done 2026-06-07. Finding applied: PAGE_FIELD_KEYS
159
+ single source of truth in Input::Page.
160
+
161
+ ---
162
+
163
+ ## Search integration
164
+
165
+ > Two modalities — see DECISIONS.md "Two search modalities" entry for full comparison.
166
+
167
+ - [x] **S.1** Wire `Query::Pages` (org search) for pre-update fetch workflow:
168
+ documented in `search_filters.md` ("The Required Pattern: Find Before Mutate") and
169
+ as a code comment on `Query::Pages`. `RegisterPreviewPages` is insufficient — no
170
+ `patchVer` or field IDs. Both docs explain the required pattern.
171
+ *Done 2026-06-07*
172
+
173
+ - [x] **S.2** `Query::RegisterPreviewPages` + `Base::PreviewPage` + `Connection::PreviewPage`.
174
+ *Done 2026-06-07 — fast ES search, includeArchived supported, presetViewId supported*
175
+
176
+ - [~] ~~Wire register-scoped page search — `Query::RegisterPreviewPages`:~~
177
+ `currentOrganization { register(id: $id) { previewPages(searchConf: $conf, includeArchived: $includeArchived, presetViewId: $presetId, ...) } }`
178
+ Returns `PreviewPage` nodes (ES-backed, fast). No field IDs or `patchVer`.
179
+ Supports archived pages via `includeArchived: Boolean` (default false).
180
+ Useful for fast metadata lookup before switching to org search for full data.
181
+
182
+ - [x] **S.3** `.ai-assistance/code/search_filters.md` — complete filter reference.
183
+ Extracted from Insomnia_2026-06-07.yaml (real production queries) + Confluence APIDOCS.
184
+ Covers: find by externalId ✓, find by state ✓, find by location_id ✓, find by date range ✓,
185
+ nested AND/OR ✓. Also documents: two query modalities, SearchConf structure, silent failure
186
+ traps, compound archive pattern, operation constants.
187
+ *Done 2026-06-07*
188
+
189
+ - [ ] **S.4** Full search filter builder — see dedicated project:
190
+ `.ai-assistance/projects/search-filter-builder/`
@@ -0,0 +1,107 @@
1
+ # Project: Search Filter Builder
2
+
3
+ **Created:** 2026-06-07
4
+ **Status:** Planning
5
+
6
+ ---
7
+
8
+ ## Problem
9
+
10
+ The EcoPortal GraphQL `searchConf` argument is a Hash type — not a typed GraphQL input
11
+ model. This means:
12
+ - No schema validation or autocomplete for filter structures
13
+ - Scripts must hand-craft raw hashes, which are error-prone and hard to read
14
+ - No reuse: changing a search target (e.g. a different `externalId`) requires cloning
15
+ and mutating the whole hash manually
16
+ - No discoverability: customers have no way to learn available filter fields from the
17
+ schema — they must consult separate documentation
18
+
19
+ Engineering has backend classes for the search model, but these are not exposed as
20
+ GraphQL input types. The Confluence API Docs space has a partial filters reference, but
21
+ it is incomplete and not tied to any code.
22
+
23
+ ---
24
+
25
+ ## Goal
26
+
27
+ Provide a first-class Ruby filter builder in `ecoportal-api-graphql` that:
28
+
29
+ 1. **Mirrors backend structure** — classes that correspond to the backend search model,
30
+ even though they have no GraphQL type equivalent (they serialise to the expected Hash).
31
+ 2. **Supports AND/OR composition** — clean DSL to express nested filter logic without
32
+ manually constructing the `or:` / `filters:` nesting.
33
+ 3. **Supports parametrization** — re-use a filter template by substituting specific
34
+ values (e.g. swap `externalId`, `locationId`, field value) without re-building
35
+ from scratch each call.
36
+ 4. **Works for both search modalities:**
37
+ - Org search: `currentOrganization { pages(searchConf: ...) }`
38
+ - Register search: `currentOrganization { register(id:) { pages(searchConf: ...) } }`
39
+ 5. **AI assistance hook (future):** The front-end already has AI-generated filter
40
+ construction for end users. This builder should be designed so that a future AI layer
41
+ can generate filter objects from natural-language descriptions. The structured classes
42
+ make this tractable — the AI outputs typed objects rather than raw hashes.
43
+
44
+ ---
45
+
46
+ ## Context
47
+
48
+ ### searchConf structure (verified from Insomnia production queries)
49
+
50
+ ```json
51
+ {
52
+ "filters": [ <filter_object>, ... ],
53
+ "sorters": { "key": "created_at", "direction": "asc" },
54
+ "query": "optional full-text string"
55
+ }
56
+ ```
57
+
58
+ `filters` can be an array (standard) or a single object (older queries).
59
+ `sorters` can be a single object or an array. Key field is `key` (not `fieldName`).
60
+
61
+ ### Filter operations (snake_case strings — confirmed)
62
+
63
+ | Operation | Structure | Notes |
64
+ |---|---|---|
65
+ | `exact_filter` | `{ key: "field", value: "val" }` | Equality match |
66
+ | `date_filter` | `{ key: "field", gte: "ISO8601", time_zone: "UTC" }` | Also: `lte` |
67
+ | `register_filter` | `{ ids: ["id1", "id2"] }` | Org search only — scope to registers |
68
+ | `and_filter` | `{ filters: [...] }` | Explicit AND group (implicit at top level) |
69
+ | `or_filter` | `{ filters: [...] }` | OR group |
70
+
71
+ Additional (from Confluence docs, not yet confirmed via Insomnia):
72
+ `in_filter`, `range_filter`, `exists_filter`, `ne_filter`
73
+
74
+ ### Known filter key fields (confirmed from Insomnia)
75
+
76
+ | key | Type | Operations confirmed |
77
+ |---|---|---|
78
+ | `external_id` | string | `exact_filter` |
79
+ | `updated_at` | datetime | `date_filter` |
80
+ | `created_at` | datetime | (in sorters; likely filterable too) |
81
+
82
+ Additional (from Confluence docs): `state`, `location_ids`, `creator_id`, `template_id`
83
+
84
+ Full reference with production examples: `.ai-assistance/code/search_filters.md`
85
+
86
+ ### Source references
87
+
88
+ - Insomnia v5 export: pending import into `.ai-assistance/tmp/insomnia_searches.json`
89
+ - Backend filter classes: accessible via the ecoPortal Rails codebase
90
+ - Front-end queries: `.graphql` files distributed across the React codebase hierarchy
91
+ - Confluence APIDOCS: partial filter reference (most common filters section)
92
+
93
+ ---
94
+
95
+ ## Design principles
96
+
97
+ - **No GraphQL type required:** Builder classes serialise to Hash — they are plain Ruby
98
+ value objects, not GraphQL input classes. No `Logic::Input` inheritance needed.
99
+ - **Composable:** `AndFilter`, `OrFilter`, `Filter`, `Sorter`, `SearchConf` as
100
+ distinct classes. Compose like: `SearchConf.new.where(externalId: 'X').or(state: :active, state: :draft)`.
101
+ - **Parametrizable:** A built `SearchConf` can be cloned with substitutions —
102
+ `search.with(externalId: 'Y')` returns a new instance with one value swapped.
103
+ - **Serializes to Hash:** `search.to_h` returns the raw hash ready to pass as
104
+ `searchConf:` argument to any query.
105
+ - **AI-compatible (future):** Class structure is simple enough for an AI model to
106
+ instantiate from a natural-language description. The AI assistance integration
107
+ is a follow-on — design should not be blocked by it.
@@ -0,0 +1,131 @@
1
+ # TODOs — Search Filter Builder
2
+
3
+ > Natural order: 1 → 2 → 3 → 4 → 5. Step 1 unblocks everything else.
4
+
5
+ ---
6
+
7
+ ## Step 1 — Ground truth: gather filter fieldNames and operator examples
8
+
9
+ - [x] **1.1** Insomnia v5 export reviewed — all search patterns documented in
10
+ `.ai-assistance/code/search_filters.md`. Confirmed operations: exact_filter,
11
+ date_filter, register_filter, and_filter, or_filter (all snake_case).
12
+ *Done 2026-06-07*
13
+
14
+ - [x] **1.2** Backend filter classes read — `app/services/new_ep/es/filters/`.
15
+ Key findings: 29 global filter operations via dynamic class routing (operation →
16
+ `"#{op.camelize}Filter"` class name); no central whitelist — filters auto-discovered.
17
+ Page-specific fieldNames: external_id, state, name, creator_id, created_at, updated_at,
18
+ template_id, id, location_id, all_location_ids, other_tags, archived, draft, mould_counter.
19
+ Full human date mode list extracted from `HumanDateFilterable` concern.
20
+ *Done 2026-06-08*
21
+
22
+ - [x] **1.3** Frontend `.graphql` + `createFilter.ts` swept — 159 unique operations found.
23
+ New operations confirmed beyond prior Insomnia list: `one_of_filter`, `none_of_filter`,
24
+ `has_any_filter`, `match_filter`, `exists_filter`, `doesnt_exist_filter`, `boolean_filter`,
25
+ `numeric_range_filter`, `isnt_exact_filter`, `not_register_filter`.
26
+ Confirmed `in_filter`/`range_filter`/`ne_filter` are NOT real operations.
27
+ *Done 2026-06-08*
28
+
29
+ - [x] **1.4** `search_filters.md` extended with:
30
+ - Full fieldName reference table for Pages (core metadata, location, tag, boolean fields)
31
+ - Extended operation reference (15 confirmed operations including new ones)
32
+ - Human date mode table (30+ modes from HumanDateFilterable)
33
+ - Parametrization patterns section (SearchConf#with — 3 examples)
34
+ - Updated Filter Builder constants with confirmed/denied status
35
+ *Done 2026-06-08*
36
+
37
+ ---
38
+
39
+ ## Step 2+3 — Design + Implement (Done 2026-06-07)
40
+
41
+ - [x] **2.1+3.1** Implemented `Input::SearchConf` with nested filter classes:
42
+ `SearchConf::Exact`, `SearchConf::DateRange`, `SearchConf::Register`,
43
+ `SearchConf::And`, `SearchConf::Or`. Plain Ruby value objects, no Logic::Input.
44
+ Chainable builder, `to_h` serialises to the expected Hash structure.
45
+ `as_json` support for transparent JSON serialisation.
46
+ *`lib/ecoportal/api/graphql/input/search_conf.rb`*
47
+
48
+ - [x] **2.2+3.3** Parametrization via `#with(**substitutions)` — returns a cloned
49
+ `SearchConf` with substituted values propagated into nested filters.
50
+ *Confirmed immutable (original not mutated)*
51
+
52
+ - [x] **4.1** 23 RSpec specs covering all filter types, builder methods, sorter
53
+ single/array convention, parametrization, nested Or propagation, as_json.
54
+ *`spec/ecoportal/api/graphql/input/search_conf_spec.rb`*
55
+
56
+ ## Remaining
57
+
58
+ - [x] **3.4** Wire `SearchConf` acceptance into queries — implemented via
59
+ `Logic::BaseQuery#normalize_variables`: auto-calls `.to_h` on any value responding to `to_h`
60
+ before passing as GraphQL variables. All queries accept `SearchConf` directly.
61
+ *Done 2026-06-07 (commit cc72902)*
62
+
63
+ - [ ] **5.1 — Fluent DSL (future):** Replace the explicit constructor API with a fluent
64
+ operator-based DSL using Ruby's `&` (AND) and `|` (OR) operators — similar to how
65
+ Arel/Sequel work. Proposed syntax:
66
+
67
+ ```ruby
68
+ # Simple field predicate
69
+ Search[:external_id].eq('ABC')
70
+ Search[:updated_at].since('2025-01-01')
71
+ Search[:state].is(:active)
72
+
73
+ # Compound (& = AND, | = OR — operator precedence is natural)
74
+ Search[:external_id].eq('ABC') & Search.in_register('REG_ID')
75
+
76
+ Search[:state].is(:active) | Search[:state].is(:draft)
77
+
78
+ # Complex: register + date AND (state active OR draft)
79
+ Search.in_register('REG_ID') &
80
+ Search[:updated_at].since('2025-01-01') &
81
+ (Search[:state].is(:active) | Search[:state].is(:draft))
82
+
83
+ # Parametrize via .with
84
+ base = Search[:external_id].eq('X') & Search.in_register('REG')
85
+ copy = base.with(external_id: 'Y')
86
+ ```
87
+
88
+ Implementation: `Search[]` returns a `FieldRef` object; `.eq`, `.is`, `.since`,
89
+ `.contains` return filter value objects; `&` and `|` return `And`/`Or` composites.
90
+ Everything still serialises via `to_h`. The current explicit API (`SearchConf::Exact.new`)
91
+ remains as the foundation — the DSL is a layer on top.
92
+
93
+ **Why operator-based over method-chain (`filter.or.attr.label.contains(...)`):**
94
+ - `&`/`|` have natural Ruby precedence — parentheses control grouping as expected
95
+ - No ambiguity between "start a new filter" vs "modify current filter"
96
+ - Composable: any sub-expression can be stored in a variable and reused
97
+ - Familiar to developers who know Arel, Sequel, or Mongoid
98
+
99
+ **AI-generation hook:** The structured DSL makes AI output easy to validate —
100
+ the AI emits `Search[:field].op(value)` expressions that can be parsed/verified
101
+ before execution.
102
+
103
+ ## ~~Steps 2–4~~ — Done (superseded by implementation)
104
+
105
+ Steps 2–4 were planning artefacts written before implementation. All are complete:
106
+ - `SearchConf`, `SearchConf::Exact/DateRange/Register/And/Or/RawFilter` — implemented
107
+ - `#to_h`, `#with`, `normalize_variables` auto-`to_h` — implemented
108
+ - 23 RSpec specs covering all types — implemented
109
+ - `search_filters.md` code-spec — implemented
110
+ - Fluent DSL (`SearchConf[:field].eq(v)`, `&`, `|`) — implemented 2026-06-08
111
+
112
+ ---
113
+
114
+ ## Step 5 — AI filter generation
115
+
116
+ - [x] **5.1** Prompt template designed — system prompt in `SearchConf::AIGenerator::SYSTEM_PROMPT`:
117
+ all 13 operations with params, 11 page fieldNames with types/values, snake_case rule,
118
+ instruction to use `date_filter mode:` for relative dates.
119
+ *Done 2026-06-09*
120
+
121
+ - [x] **5.2** `SearchConf.from_description(text, api_key:, model:, register_id:, context:, validate:)`.
122
+ Claude tool use (structured output via `build_search_conf` JSON schema).
123
+ `SearchConf::AIGenerator` in `input/search_conf/ai_generator.rb`.
124
+ Lazy require — `anthropic` gem not a hard dependency; helpful LoadError if missing.
125
+ `.env` loaded automatically in `spec_helper.rb` via dotenv.
126
+ Live tests tagged `:live`: `bundle exec rspec ...ai_generator_spec.rb --tag live`
127
+ *Done 2026-06-09*
128
+
129
+ - [ ] **5.3** Evaluate: compare AI-generated filters against hand-crafted equivalents
130
+ using the Insomnia sample set as ground truth. Run live tests, collect outputs,
131
+ compare to manually built `SearchConf` instances.