ecoportal-api-graphql 1.3.13 → 1.3.16

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 (275) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -0
  3. data/lib/ecoportal/api/common/graphql/client.rb +0 -2
  4. data/lib/ecoportal/api/common/graphql/http_client.rb +6 -0
  5. data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -17
  6. data/lib/ecoportal/api/graphql/base/file_container.rb +14 -14
  7. data/lib/ecoportal/api/graphql/base/location_classification_type.rb +15 -15
  8. data/lib/ecoportal/api/graphql/base/location_structure.rb +27 -27
  9. data/lib/ecoportal/api/graphql/base/organization.rb +15 -15
  10. data/lib/ecoportal/api/graphql/base/page/task.rb +24 -0
  11. data/lib/ecoportal/api/graphql/base/page.rb +17 -16
  12. data/lib/ecoportal/api/graphql/base/template.rb +35 -0
  13. data/lib/ecoportal/api/graphql/base.rb +37 -35
  14. data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +41 -41
  15. data/lib/ecoportal/api/graphql/compat/response.rb +35 -35
  16. data/lib/ecoportal/api/graphql/connection/action.rb +11 -11
  17. data/lib/ecoportal/api/graphql/connection/action_category.rb +11 -11
  18. data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +11 -11
  19. data/lib/ecoportal/api/graphql/connection/page.rb +11 -11
  20. data/lib/ecoportal/api/graphql/connection/person_member.rb +11 -11
  21. data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -11
  22. data/lib/ecoportal/api/graphql/connection.rb +17 -17
  23. data/lib/ecoportal/api/graphql/file_upload/client.rb +21 -10
  24. data/lib/ecoportal/api/graphql/fragment/field_configuration.rb +38 -0
  25. data/lib/ecoportal/api/graphql/fragment/force.rb +14 -10
  26. data/lib/ecoportal/api/graphql/fragment/location_node.rb +26 -26
  27. data/lib/ecoportal/api/graphql/fragment/page_task.rb +39 -0
  28. data/lib/ecoportal/api/graphql/fragment/template.rb +51 -0
  29. data/lib/ecoportal/api/graphql/fragment.rb +3 -0
  30. data/lib/ecoportal/api/graphql/input/contractor_entity.rb +14 -14
  31. data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -16
  32. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -18
  33. data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -21
  34. data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -28
  35. data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -18
  36. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -23
  37. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -20
  38. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -20
  39. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -24
  40. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -20
  41. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -20
  42. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -20
  43. data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -18
  44. data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -18
  45. data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -18
  46. data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -251
  47. data/lib/ecoportal/api/graphql/input.rb +24 -24
  48. data/lib/ecoportal/api/graphql/logic/connection.rb +34 -34
  49. data/lib/ecoportal/api/graphql/model/page/task.rb +16 -0
  50. data/lib/ecoportal/api/graphql/model/page.rb +16 -15
  51. data/lib/ecoportal/api/graphql/model/person_member.rb +15 -15
  52. data/lib/ecoportal/api/graphql/model/template.rb +15 -0
  53. data/lib/ecoportal/api/graphql/model.rb +32 -30
  54. data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -45
  55. data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -40
  56. data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +14 -14
  57. data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -40
  58. data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +38 -40
  59. data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +38 -40
  60. data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +38 -40
  61. data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +7 -7
  62. data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +8 -7
  63. data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +38 -40
  64. data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -40
  65. data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -40
  66. data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -40
  67. data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +34 -35
  68. data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -35
  69. data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -36
  70. data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -40
  71. data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -13
  72. data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -46
  73. data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +1 -1
  74. data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +1 -1
  75. data/lib/ecoportal/api/graphql/mutation.rb +20 -20
  76. data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +14 -14
  77. data/lib/ecoportal/api/graphql/payload/ok_payload.rb +36 -21
  78. data/lib/ecoportal/api/graphql/payload/page/draft.rb +26 -13
  79. data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -13
  80. data/lib/ecoportal/api/graphql/payload/page/review_task_batch.rb +23 -0
  81. data/lib/ecoportal/api/graphql/payload/page.rb +20 -19
  82. data/lib/ecoportal/api/graphql/payload/preset_view.rb +15 -11
  83. data/lib/ecoportal/api/graphql/payload/register.rb +15 -11
  84. data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +1 -1
  85. data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +1 -1
  86. data/lib/ecoportal/api/graphql/payload/template/update_information.rb +1 -1
  87. data/lib/ecoportal/api/graphql/payload.rb +23 -23
  88. data/lib/ecoportal/api/graphql/query/location_structures.rb +57 -57
  89. data/lib/ecoportal/api/graphql/query/page.rb +45 -45
  90. data/lib/ecoportal/api/graphql/query/page_with_forces.rb +9 -3
  91. data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +9 -3
  92. data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -78
  93. data/lib/ecoportal/api/graphql/query.rb +27 -27
  94. data/lib/ecoportal/api/graphql.rb +9 -5
  95. data/lib/ecoportal/api/graphql_version.rb +1 -1
  96. metadata +9 -180
  97. data/.ai-assistance/bridge/CLAUDE.md +0 -338
  98. data/.ai-assistance/bridge/archive/.gitkeep +0 -0
  99. data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +0 -29
  100. data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +0 -18
  101. data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +0 -42
  102. data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +0 -115
  103. data/.ai-assistance/bridge/context/gemini-review-prompt.txt +0 -48
  104. data/.ai-assistance/bridge/context/gemini-review-response.md +0 -104
  105. data/.ai-assistance/bridge/context/project.md +0 -42
  106. data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
  107. data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
  108. data/.ai-assistance/bridge/outbox/request-for-standards-discovery.md +0 -48
  109. data/.ai-assistance/bridge/queue/.gitkeep +0 -1
  110. data/.ai-assistance/capabilities/CLAUDE.md +0 -27
  111. data/.ai-assistance/capabilities/assumptions-log.md +0 -80
  112. data/.ai-assistance/capabilities/code.md +0 -47
  113. data/.ai-assistance/capabilities/connectors.md +0 -37
  114. data/.ai-assistance/capabilities/cowork.md +0 -55
  115. data/.ai-assistance/code/OVERVIEW.md +0 -155
  116. data/.ai-assistance/code/data_fields.md +0 -242
  117. data/.ai-assistance/code/dependencies.md +0 -151
  118. data/.ai-assistance/code/diff_as_input.md +0 -234
  119. data/.ai-assistance/code/diff_pairing_engine.md +0 -243
  120. data/.ai-assistance/code/diff_service_deep_dive.md +0 -192
  121. data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +0 -55
  122. data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +0 -181
  123. data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +0 -192
  124. data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +0 -147
  125. data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +0 -277
  126. data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +0 -200
  127. data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +0 -228
  128. data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +0 -197
  129. data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +0 -243
  130. data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +0 -196
  131. data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +0 -132
  132. data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +0 -187
  133. data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +0 -236
  134. data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +0 -183
  135. data/.ai-assistance/code/ecoportal_schema_reference.md +0 -240
  136. data/.ai-assistance/code/filter_contract_matrix.md +0 -177
  137. data/.ai-assistance/code/graphql_domain_knowledge.md +0 -240
  138. data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +0 -71
  139. data/.ai-assistance/code/refactoring/opportunities.md +0 -251
  140. data/.ai-assistance/code/schema_analysis.md +0 -321
  141. data/.ai-assistance/code/search_filters.md +0 -868
  142. data/.ai-assistance/code/spec_coverage.md +0 -73
  143. data/.ai-assistance/code/template_diff_pairing_domain.md +0 -175
  144. data/.ai-assistance/code/workflow-command-guide.md +0 -466
  145. data/.ai-assistance/code/workflow-space.md +0 -353
  146. data/.ai-assistance/conventions/CLAUDE.md +0 -30
  147. data/.ai-assistance/conventions/code-working-tree-protocol.md +0 -199
  148. data/.ai-assistance/conventions/gitignore-rules.md +0 -42
  149. data/.ai-assistance/conventions/permission-guidance.md +0 -120
  150. data/.ai-assistance/integrations/README.md +0 -70
  151. data/.ai-assistance/integrations/gitkraken-mcp.md +0 -107
  152. data/.ai-assistance/integrations/gitlab-mcp.md +0 -123
  153. data/.ai-assistance/integrations/local-git.md +0 -60
  154. data/.ai-assistance/local_paths.example.md +0 -17
  155. data/.ai-assistance/projects/TODO.md +0 -118
  156. data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +0 -168
  157. data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +0 -60
  158. data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +0 -267
  159. data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +0 -53
  160. data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +0 -102
  161. data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +0 -139
  162. data/.ai-assistance/projects/compat-layer-audit/COMPAT_AUDIT.md +0 -244
  163. data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +0 -93
  164. data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +0 -244
  165. data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +0 -266
  166. data/.ai-assistance/projects/eco-helpers-compat/TODO.md +0 -86
  167. data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +0 -101
  168. data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +0 -177
  169. data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +0 -161
  170. data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +0 -125
  171. data/.ai-assistance/projects/ooze-graphql-native-migration/INVENTORY.md +0 -136
  172. data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +0 -126
  173. data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +0 -261
  174. data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +0 -122
  175. data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +0 -148
  176. data/.ai-assistance/projects/page-model/DECISIONS.md +0 -245
  177. data/.ai-assistance/projects/page-model/TODO.md +0 -190
  178. data/.ai-assistance/projects/qa-services-delivery/DECISIONS.md +0 -93
  179. data/.ai-assistance/projects/qa-services-delivery/INTENT.md +0 -76
  180. data/.ai-assistance/projects/qa-services-delivery/PHASE3-SCOPE.md +0 -115
  181. data/.ai-assistance/projects/qa-services-delivery/ROADMAP.md +0 -99
  182. data/.ai-assistance/projects/qa-services-delivery/TODO.md +0 -81
  183. data/.ai-assistance/projects/search-filter-builder/INTENT.md +0 -107
  184. data/.ai-assistance/projects/search-filter-builder/TODO.md +0 -131
  185. data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +0 -87
  186. data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +0 -108
  187. data/.ai-assistance/projects/template-diff-deploy/INTENT.md +0 -12
  188. data/.ai-assistance/projects/template-diff-deploy/TODO.md +0 -9
  189. data/.ai-assistance/projects/template-maintenance/DESIGN.md +0 -134
  190. data/.ai-assistance/projects/template-maintenance/PHASE0-FINDINGS.md +0 -93
  191. data/.ai-assistance/projects/template-maintenance/README.md +0 -14
  192. data/.ai-assistance/projects/template-model-logic/CATEGORY_CATALOG.md +0 -236
  193. data/.ai-assistance/projects/template-model-logic/CLASSIFICATION_SPIKE.md +0 -243
  194. data/.ai-assistance/projects/template-model-logic/DESIGN_NOTE.md +0 -154
  195. data/.ai-assistance/projects/workflow-space/TODO.md +0 -213
  196. data/.ai-assistance/reinstall-claude-desktop-windows.md +0 -136
  197. data/.ai-assistance/scripts/CLAUDE.md +0 -150
  198. data/.ai-assistance/scripts/bridge-inbox-check.sh +0 -75
  199. data/.ai-assistance/scripts/bridge-init.sh +0 -86
  200. data/.ai-assistance/scripts/bridge-status.sh +0 -44
  201. data/.ai-assistance/scripts/capabilities-check.ts +0 -104
  202. data/.ai-assistance/scripts/check-outbox.sh +0 -43
  203. data/.ai-assistance/scripts/dep_graph.rb +0 -91
  204. data/.ai-assistance/scripts/lock-acquire.sh +0 -103
  205. data/.ai-assistance/scripts/lock-multi.sh +0 -124
  206. data/.ai-assistance/scripts/lock-queue.sh +0 -94
  207. data/.ai-assistance/scripts/setup-mcps.test.ts +0 -188
  208. data/.ai-assistance/scripts/setup-mcps.ts +0 -234
  209. data/.ai-assistance/scripts/task-complete.ts +0 -74
  210. data/.ai-assistance/scripts/task-create.ts +0 -75
  211. data/.ai-assistance/scripts/task-read.ts +0 -125
  212. data/.ai-assistance/scripts/token-logger.js +0 -220
  213. data/.ai-assistance/scripts/token-report.ts +0 -158
  214. data/.ai-assistance/scripts/token-session-start.js +0 -66
  215. data/.ai-assistance/skills/ai-instructions/SKILL.md +0 -48
  216. data/.ai-assistance/skills/code-specs/SKILL.md +0 -69
  217. data/.ai-assistance/skills/corporate-policies/SKILL.md +0 -201
  218. data/.ai-assistance/skills/dep-graph/SKILL.md +0 -139
  219. data/.ai-assistance/skills/ep-ai-manager/SKILL.md +0 -417
  220. data/.ai-assistance/skills/gemini-assist/SKILL.md +0 -63
  221. data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +0 -205
  222. data/.ai-assistance/skills/gemini-assist/gemini_ask.py +0 -1
  223. data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +0 -240
  224. data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +0 -25
  225. data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +0 -261
  226. data/.ai-assistance/skills/procedural-memory/SKILL.md +0 -319
  227. data/.ai-assistance/skills/project-cycle/SKILL.md +0 -177
  228. data/.ai-assistance/skills/project-self-docs/SKILL.md +0 -181
  229. data/.ai-assistance/skills/project-self-docs/scripts/self_docs_scan.py +0 -378
  230. data/.ai-assistance/skills/refactor/SKILL.md +0 -62
  231. data/.ai-assistance/skills/rubocop/SKILL.md +0 -93
  232. data/.ai-assistance/skills/ruby-scripting/SKILL.md +0 -215
  233. data/.ai-assistance/skills/spec-generation/SKILL.md +0 -72
  234. data/.ai-assistance/standards-version.json +0 -22
  235. data/.ai-assistance/token-budget.json +0 -32
  236. data/.ai-assistance/version.json +0 -39
  237. data/.claude/settings.json +0 -150
  238. data/.env.example +0 -18
  239. data/.gitattributes +0 -15
  240. data/.gitignore +0 -37
  241. data/.gitlab-ci.yml +0 -45
  242. data/.markdownlint.json +0 -4
  243. data/.rspec +0 -3
  244. data/.rubocop.yml +0 -121
  245. data/.ruby-version +0 -1
  246. data/.yardopts +0 -10
  247. data/CLAUDE.md +0 -243
  248. data/Gemfile +0 -30
  249. data/Rakefile +0 -90
  250. data/bin/console +0 -14
  251. data/bin/setup +0 -8
  252. data/bin/setup.ps1 +0 -6
  253. data/docs/self-docs/ARCHITECTURE.md +0 -88
  254. data/docs/self-docs/CHANGES.jsonl +0 -12
  255. data/docs/self-docs/COMPLIANCE.md +0 -79
  256. data/docs/self-docs/CONVENTIONS.md +0 -74
  257. data/docs/self-docs/INTEGRATIONS.md +0 -65
  258. data/docs/self-docs/OPERATIONS.md +0 -76
  259. data/docs/self-docs/OVERVIEW.md +0 -64
  260. data/docs/self-docs/STATUS.md +0 -73
  261. data/docs/self-docs/self-docs-index.json +0 -51
  262. data/docs/worklog.md +0 -977
  263. data/ecoportal-api-graphql.gemspec +0 -40
  264. data/scripts/auto-worker-scheduler.sh +0 -386
  265. data/tests/actions_get.rb +0 -7
  266. data/tests/contractor_entities_get.rb +0 -20
  267. data/tests/contractor_entity_create.rb +0 -19
  268. data/tests/contractor_entity_udpate.rb +0 -20
  269. data/tests/dump_page_model.rb +0 -74
  270. data/tests/dump_template_model.rb +0 -90
  271. data/tests/loc_structure_get.rb +0 -10
  272. data/tests/loc_structure_update.rb +0 -51
  273. data/tests/loc_structures_get.rb +0 -15
  274. data/tests/local_libs.rb +0 -20
  275. data/tests/validate_queries.rb +0 -127
@@ -1,277 +0,0 @@
1
- # EcoPortal GraphQL Queries and Mutations
2
-
3
- ---
4
-
5
- ## Query Root (`currentOrganization`)
6
-
7
- All queries nest under `currentOrganization`. There is no top-level resource query —
8
- everything is scoped to the authenticated user's organisation.
9
-
10
- ```graphql
11
- {
12
- currentOrganization {
13
- id
14
- # All other queries go here
15
- }
16
- }
17
- ```
18
-
19
- ---
20
-
21
- ## Page Queries
22
-
23
- ### `page(id: ID!)` → `PageUnion`
24
-
25
- Fetches a single page by ID. Returns `BasicPage | PhasedPage`.
26
-
27
- ```graphql
28
- query GetPage($id: ID!) {
29
- currentOrganization {
30
- page(id: $id) {
31
- ...PageFields # core metadata
32
- ...PhasedPageFields # stage index (if phased)
33
- }
34
- }
35
- }
36
- ```
37
-
38
- **Gem class:** `Query::Page`
39
- **For compat (full data):** Use `...CommonPageUnion` fragment (includes all field data).
40
-
41
- ### `pages(...)` → `PageUnionConnection`
42
-
43
- Org-wide paginated page search. Returns full `PageUnion` from DB.
44
-
45
- **Arguments:**
46
- - `searchConf: Search` — filter/sort/query configuration
47
- - `templatesOnly: Boolean` — restrict to templates only
48
- - `showHiddenData: Boolean` — include hidden fields
49
- - `after/before/first/last` — cursor pagination
50
-
51
- **Performance note:** ~3 seconds per page on render (DB-backed). Avoid large result sets.
52
-
53
- ```graphql
54
- query OrgSearchPages($searchConf: Search, $first: Int, $after: String) {
55
- currentOrganization {
56
- pages(searchConf: $searchConf, first: $first, after: $after) {
57
- totalCount
58
- pageInfo { hasNextPage endCursor }
59
- nodes { ...PageFields }
60
- }
61
- }
62
- }
63
- ```
64
-
65
- **Gem class:** `Query::Pages`
66
-
67
- ### `templates(...)` → `PageConnection`
68
-
69
- Lists templates (pages with `templatesOnly: true`). Same arguments as `pages`.
70
-
71
- **Gem class:** `Query::Templates`
72
-
73
- ### `register(id:) { previewPages(...) }` → `PreviewPageConnection`
74
-
75
- ES-backed fast register search. Returns `PreviewPage` — metadata only, no field IDs.
76
-
77
- **Arguments on `previewPages`:**
78
- - `searchConf: Search`
79
- - `includeArchived: Boolean` (default false)
80
- - `presetViewId: ID` — apply a register PageView preset
81
-
82
- **Performance:** Sub-second (Elasticsearch). Use for metadata lookup.
83
- **Limitation:** No `patchVer`, no field IDs. Cannot be used for mutation input building.
84
-
85
- ```graphql
86
- query RegisterSearch($registerId: ID!, $search: Search, $includeArchived: Boolean) {
87
- currentOrganization {
88
- register(id: $registerId) {
89
- previewPages(searchConf: $search, includeArchived: $includeArchived) {
90
- totalCount
91
- pageInfo { hasNextPage endCursor }
92
- nodes { id uid name state archived externalId createdAt { dateTime } }
93
- }
94
- }
95
- }
96
- }
97
- ```
98
-
99
- **Gem class:** `Query::RegisterPreviewPages`
100
-
101
- ### `pageDelta(deltaInput: [DeltaInput!]!, searchConf: Search)` → `[DeltaResult!]!`
102
-
103
- Cache-invalidation query. Given a list of `{ id, deltaAt }` pairs, returns which pages
104
- have changed since the given timestamp.
105
-
106
- **`DeltaResult`:** `{ id: ID!, delta: DeltaEnum! }` where `DeltaEnum = UPDATED | NOT_FOUND | NEW`
107
-
108
- **Gem class:** `Query::PageDelta`
109
-
110
- ---
111
-
112
- ## Page Mutations
113
-
114
- ### `buildPageFromTemplate(input: BuildFromTemplateInput!)` → `BuildFromTemplatePayload`
115
-
116
- **Step (a) of the 2-step create sequence.** Builds a draft without persisting it.
117
- Returns a `PageUnion` with server-assigned field IDs (the template's field IDs mapped
118
- to new instance IDs). Use these IDs to populate `dataFields.updates` in `createFromTemplate`.
119
-
120
- ```graphql
121
- mutation BuildPage($input: BuildFromTemplateInput!) {
122
- buildPageFromTemplate(input: $input) {
123
- errors { details fullMessages }
124
- item { ...CommonPageUnion }
125
- }
126
- }
127
- ```
128
-
129
- **Variables:** `{ templateId: "...", showHiddenData: true }`
130
-
131
- ### `createPageFromTemplate(input: CreateFromTemplateInput!)` → `CreateFromTemplatePayload`
132
-
133
- **Step (b) of the 2-step create sequence.** Creates the page from a filled draft.
134
-
135
- **Key `CreateFromTemplateInput` fields:**
136
- - `templateId: ID!`
137
- - `dataFields: DataFieldOneToManyInput!` — field values to set on creation
138
- - `page: PageInput` — page-level fields (name, state, locations, etc.)
139
- - `showHiddenData: Boolean`
140
- - `stageId: ID` — target stage
141
- - `submit: Boolean` — submit immediately
142
- - `draft: Boolean` — create as draft
143
-
144
- ### `updatePage(input: UpdatePageInput!)` → `UpdatePagePayload`
145
-
146
- Updates an existing page.
147
-
148
- **Key `UpdatePageInput` fields:**
149
- - `id: ID!`
150
- - `patchVer: Int` — **MUST match current page patchVer**
151
- - `page: PageInput` — page-level fields to change
152
- - `dataFields: DataFieldOneToManyInput` — field updates and/or additions
153
- - `stageId: ID` — target specific stage (required for stage submit/close-out)
154
- - `submit: Boolean` — submit the stage (triggers task assignment server-side)
155
- - `publish: Boolean` — publish a draft page
156
- - `showHiddenData: Boolean`
157
-
158
- **DataFieldOneToManyInput:**
159
- ```json
160
- {
161
- "updates": [{ "plainText": { "id": "FIELD_ID", "value": "..." } }],
162
- "additions": [{ "date": { "id": "NEW_ID", "value": "..." } }],
163
- "deletions": ["FIELD_ID_TO_DELETE"]
164
- }
165
- ```
166
-
167
- ### `archivePage(input: ArchivePageInput!)` / `unarchivePage(...)` → payload with `item: PageUnion`
168
-
169
- Archive/unarchive a page.
170
-
171
- **Production pattern (compound mutation):**
172
- When a page has a unique `externalId`, archive the externalId first to prevent conflicts
173
- on future re-creation, then archive:
174
-
175
- ```graphql
176
- mutation ArchivePage($id: ID!, $blank_external_id: Boolean = true) {
177
- updatePage(input: { id: $id, page: { externalId: null } })
178
- @include(if: $blank_external_id) {
179
- item { id externalId }
180
- }
181
- archivePage(input: { id: $id }) {
182
- item { id archived }
183
- errors { details fullMessages }
184
- }
185
- }
186
- ```
187
-
188
- This compound mutation (two in one request) was designed for stateless middleware
189
- clients that cannot sequence separate HTTP calls.
190
-
191
- ---
192
-
193
- ## Fragments
194
-
195
- ### `PageFields` (on `BasePageInterface`)
196
-
197
- Core metadata fragment — always include `__typename` for `PageUnion` dispatch.
198
-
199
- Fields: `__typename id name uid icon patchVer state archived archivedAt draft externalId
200
- timeZone otherTags taskPriority sourceTemplateId createdAt updatedAt creatorName locations { ...LocationNode }`
201
-
202
- **Gem:** `Fragment::PageFields` in `fragment/page.rb`
203
-
204
- ### `BasicPageFields` (on `BasicPage`)
205
-
206
- Sections available directly on BasicPage:
207
- ```graphql
208
- fragment BasicPageFields on BasicPage {
209
- sections { __typename ... on ContentSection { id heading } ... on SplitSection { id heading } }
210
- }
211
- ```
212
-
213
- **Gem:** `Fragment::BasicPageFields`
214
-
215
- ### `PhasedPageFields` (on `PhasedPage`)
216
-
217
- Stage index for phased pages (lightweight — no data fields):
218
- ```graphql
219
- fragment PhasedPageFields on PhasedPage {
220
- stagesIndex { id name ordering state started hasCompleteTask hasReviewTask }
221
- currentStage { id name ordering state }
222
- activeStages { id name ordering }
223
- }
224
- ```
225
-
226
- **Gem:** `Fragment::PhasedPageFields`
227
-
228
- ### `CommonPageUnion` (on `PageUnion`) — THE STANDARD FRAGMENT
229
-
230
- The comprehensive fragment covering all customer integration patterns.
231
- Located in `Fragment::Pages` (sub-namespace, accessed via `fragment.pages.assemble(:CommonPageUnion)`).
232
-
233
- **Boolean variables controlling payload:**
234
-
235
- | Variable | Purpose |
236
- |---|---|
237
- | `$content` | Include field values, stage completion info, files, people |
238
- | `$only_content` | Exclude structural/config — content values only |
239
- | `$fields` | Include field definitions (labels, types, options — needed for mutation input building) |
240
-
241
- **Three use cases:**
242
- 1. **Build from template** — `$fields: true, $content: false` → get field structure + IDs
243
- 2. **Fetch before update** — `$content: true, $fields: true` → current values + IDs for mutation
244
- 3. **Verify after create/update** — `$content: true, $only_content: true` → confirm result
245
-
246
- **Fragment source:** `lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb`
247
- **Documented in:** Jira API Docs space (Confluence APIDOCS)
248
-
249
- ### `LocationNode` (on `LocationNodeInterface`)
250
-
251
- Location node fragment with ancestors and classifications. Automatically assembled
252
- when `...LocationNode` appears in a query via the recursive `assemble_fragments` mechanism.
253
-
254
- ---
255
-
256
- ## Fragment Assembly (How It Works)
257
-
258
- The gem scans query strings for `...FragmentName` spreads and assembles the needed
259
- fragment definitions RECURSIVELY. A fragment that references another fragment
260
- (e.g. `PageFields → ...LocationNode`) is resolved automatically.
261
-
262
- Sub-namespaces (`Fragment::Pages`) are registered via `Fragment.register_namespace(Pages)`
263
- so `Fragment.assemble(:CommonPageUnion)` finds it.
264
-
265
- ---
266
-
267
- ## Other Key Queries
268
-
269
- ### `contractorEntities(...)` → `ContractorEntityConnection`
270
- ### `actionCategories(...)` → `ActionCategoryConnection`
271
- ### `action(id:)` → `Action`
272
- ### `actions(...)` → `ActionConnection`
273
- ### `locationStructure(id:)` → `LocationStructure`
274
- ### `locationStructures(...)` → `LocationStructureConnection`
275
- ### `registers(...)` → register list
276
- ### `personMembers(...)` → `PersonMemberConnection`
277
- ### `pageDelta(...)` → cache invalidation
@@ -1,200 +0,0 @@
1
- # EcoPortal Page Workflows
2
-
3
- ---
4
-
5
- ## The 2-Step Create Sequence
6
-
7
- Creating a page from a template ALWAYS requires two steps because MongoDB assigns field
8
- IDs at creation time — the client must know these IDs to populate field values.
9
-
10
- ### Step A: Build from Template
11
-
12
- ```ruby
13
- # GraphQL
14
- payload = Mutation::Page::BuildFromTemplate.new(client).query(
15
- input: { templateId: template_id, showHiddenData: true }
16
- ) do
17
- item { spread :CommonPageUnion }
18
- end
19
- draft = payload.item
20
- ```
21
-
22
- This returns a `PageUnion` with:
23
- - All field definitions for the template (labels, types, options)
24
- - Server-assigned field IDs (instance IDs, NOT template field IDs)
25
- - Default values where configured
26
- - Stage structure (for PhasedPage)
27
-
28
- **Important:** The returned page is NOT persisted yet.
29
-
30
- ### Step B: Fill and Create
31
-
32
- ```ruby
33
- # Modify fields using the instance field IDs from the draft
34
- draft.components.get_by_name('Job Number').value = 'JOB-001'
35
- draft.components.get_by_type(:select).find { |f| f.label == 'Status' }.select_option('Active')
36
-
37
- # Build field updates from dirty fields
38
- field_updates = draft.data_fields_updates # → [DataFieldInput, ...]
39
-
40
- # Create the page
41
- payload = Mutation::Page::CreateFromTemplate.new(client).query(
42
- input: {
43
- templateId: template_id,
44
- dataFields: { updates: field_updates }, # uses instance field IDs from draft
45
- page: { externalId: 'EXT-001', name: 'My Record' }
46
- }
47
- )
48
- ref = Compat::PageReference.new(payload.item)
49
- # ref.page_id → the created page's ID
50
- # ref.active_stage_id → ID of the first active stage
51
- ```
52
-
53
- ---
54
-
55
- ## The 2-Step Update Sequence
56
-
57
- Updating a page ALWAYS requires fetching first to get:
58
- 1. `patchVer` — required for optimistic concurrency
59
- 2. Current field IDs — for building `dataFields.updates`
60
- 3. Current field values — for computing changes
61
-
62
- ### Step A: Fetch Current Page
63
-
64
- ```ruby
65
- page = Query::Page.new(client).query(id: page_id) do
66
- spread :CommonPageUnion
67
- end
68
- # OR via compat layer:
69
- page = graphql.pages.get(page_id)
70
- ```
71
-
72
- ### Step B: Modify and Update
73
-
74
- ```ruby
75
- # Modify page-level fields
76
- page.name = 'Updated Name'
77
- page.external_id = 'NEW-EXT-001'
78
-
79
- # Modify data fields
80
- page.components.get_by_name('Summary').value = 'Updated summary'
81
-
82
- # Send update
83
- input = Input::Page::Update.from_model(page)
84
- # input[:patchVer] — injected automatically from page.patchVer
85
- # input[:page] — page-level changes
86
- # input[:dataFields] — field-level changes
87
- payload = Builder::Page.new(client).update(input: input)
88
- ```
89
-
90
- **Why patchVer matters:** If two scripts update the same page concurrently, the second
91
- update will fail with a stale-patchVer error. Re-fetch the page to get the new patchVer.
92
-
93
- ---
94
-
95
- ## Stage Submit / Close-Out Sequence
96
-
97
- When a stage needs to be submitted (triggering task assignment on the next stage):
98
-
99
- ```ruby
100
- # Fetch page with full stage data
101
- page = graphql.pages.get(page_id)
102
-
103
- # Get stage view for the specific stage
104
- stage_view = graphql.pages.get(page_id, stage_id: 's1_id')
105
- # stage_view.sections, stage_view.components → filtered to this stage only
106
-
107
- # Fill in field data for this stage
108
- stage_view.components.get_by_name('Risk Level').select_option('High')
109
-
110
- # Submit the stage
111
- input = Input::Page::Update.from_model(
112
- stage_view,
113
- stage_id: 's1_id',
114
- submit: true
115
- )
116
- payload = Builder::Page.new(client).update(input: input)
117
- ```
118
-
119
- **What `submit: true` triggers server-side:**
120
- - Stage state changes to `complete`
121
- - Next fill-in task (`CompletePage`) is created and assigned per workflow rules
122
- - Or if review is configured: `ReviewPage` task is created
123
- - People fields scoped to the next stage may generate access grants
124
-
125
- **Important:** Without `stageId`, the server may not correctly associate the submit
126
- with the right stage. Always include `stageId` when submitting a specific stage.
127
-
128
- ---
129
-
130
- ## Archive Sequence
131
-
132
- Standard archive (no externalId concern):
133
- ```ruby
134
- payload = Builder::Page.new(client).archive(input: { id: page_id })
135
- ```
136
-
137
- With externalId blanking (production pattern for deduplication safety):
138
- ```graphql
139
- mutation ArchivePage($id: ID!, $blank_external_id: Boolean = true) {
140
- updatePage(input: { id: $id, page: { externalId: null } })
141
- @include(if: $blank_external_id) {
142
- item { id }
143
- }
144
- archivePage(input: { id: $id }) {
145
- item { id archived }
146
- errors { details fullMessages }
147
- }
148
- }
149
- ```
150
-
151
- ---
152
-
153
- ## Find-Before-Create (Deduplication Pattern)
154
-
155
- To prevent creating duplicate pages based on a unique identifier:
156
-
157
- ```ruby
158
- # Step 1: Search for existing page
159
- conf = SearchConf.new
160
- .filter(SearchConf::Register.new(register_id))
161
- .filter(SearchConf::Exact.new(:external_id, 'EXT-001'))
162
-
163
- result = org.pages(searchConf: conf.to_h, first: 1)
164
- existing = result.nodes.first
165
-
166
- if existing
167
- # Page exists — update it
168
- page = graphql.pages.get(existing.id)
169
- # ... modify and update
170
- else
171
- # Page doesn't exist — create it
172
- draft = graphql.pages.get_new(template_id)
173
- # ... fill and create
174
- end
175
- ```
176
-
177
- **Why org search (not register search):** Register search (`previewPages`) is ES-backed
178
- and fast, but returns `PreviewPage` without field IDs or `patchVer`. For update workflows,
179
- always use org search (`pages`) with `register_filter` to get the full page.
180
-
181
- ---
182
-
183
- ## Simulate / Dry Run Mode (eco-helpers Pattern)
184
-
185
- eco-helpers scripts support a dry-run mode where changes are computed but not sent:
186
-
187
- ```ruby
188
- # In eco-helpers, dirty? checks for pending changes
189
- if dirty?(page)
190
- if simulate?
191
- puts "Would update: #{pages.get_body(page).inspect}"
192
- else
193
- pages.update(page)
194
- page.consolidate! # reset dirty tracking
195
- end
196
- end
197
- ```
198
-
199
- The `get_body(page)` method returns `page.as_update` — the computed change diff.
200
- `consolidate!` resets the `original_doc` baseline so the page is no longer dirty.
@@ -1,228 +0,0 @@
1
- # EcoPortal Search and Filters
2
-
3
- ---
4
-
5
- ## Two Search Modalities
6
-
7
- ### 1. Org Search — `currentOrganization { pages(...) }`
8
-
9
- **Data source:** PostgreSQL/MongoDB — full DB render
10
- **Returns:** `PageUnion` (BasicPage | PhasedPage) — complete data
11
- **Performance:** ~3 seconds per page (API-layer assembly)
12
- **Supports archived:** Yes (via `searchConf` state filter)
13
- **Has field IDs:** Yes — use for pre-update fetch
14
- **patchVer:** Yes — required for mutations
15
-
16
- Use when you need full field data to build mutation inputs.
17
-
18
- **Arguments:**
19
- - `searchConf: Search` — filter/sort/query
20
- - `templatesOnly: Boolean` — restrict to templates
21
- - `showHiddenData: Boolean` — include hidden fields
22
- - `first/last/after/before` — cursor pagination
23
-
24
- **Key `searchConf` filter for register-scoping within org search:**
25
- ```json
26
- { "operation": "register_filter", "params": { "ids": ["REGISTER_ID"] } }
27
- ```
28
- This scopes the org search to a register without switching to `previewPages`.
29
-
30
- ### 2. Register Search — `currentOrganization { register(id:) { previewPages(...) } }`
31
-
32
- **Data source:** Elasticsearch — search index snapshot
33
- **Returns:** `PreviewPage` — metadata only (id, name, state, locations, dataFields as previews)
34
- **Performance:** Sub-second (ES-powered)
35
- **Supports archived:** Yes — `includeArchived: Boolean` parameter (default false)
36
- **Has field IDs:** NO — cannot build mutation inputs from this
37
- **patchVer:** NO
38
-
39
- Use for fast metadata lookup (find page IDs, check existence, list pages).
40
-
41
- **Additional arguments:**
42
- - `presetViewId: ID` — apply a register PageView preset configuration
43
- - `searchConf: Search`
44
- - `includeArchived: Boolean`
45
-
46
- **Field name:** `previewPages` (not `pages`) — common confusion point.
47
-
48
- ---
49
-
50
- ## SearchConf Structure
51
-
52
- `searchConf` is a `Hash` type (`Search` in the schema) — NOT a typed GraphQL input.
53
- Engineering has backend classes but they are not exposed as GraphQL types.
54
-
55
- ```json
56
- {
57
- "filters": [ <filter_object>, ... ],
58
- "sorters": { "key": "created_at", "direction": "asc" },
59
- "query": "optional full-text search string"
60
- }
61
- ```
62
-
63
- **Note on sorters:** Can be a single object OR an array. Key field is `key` (not `fieldName`).
64
-
65
- ---
66
-
67
- ## Filter Operations
68
-
69
- **ALL operation names are snake_case strings.** If CamelCase is used (e.g. `ExactFilter`),
70
- the server returns NO error AND NO results — completely silent failure. This is one of
71
- the hardest bugs for customers to diagnose.
72
-
73
- **`filters` should always be an Array.** The server normalises a single object to an
74
- array internally, but always use array form for consistency.
75
-
76
- ### `exact_filter`
77
-
78
- Equality match on a field.
79
-
80
- ```json
81
- { "operation": "exact_filter", "params": { "key": "external_id", "value": "EXT-001" } }
82
- ```
83
-
84
- Common keys: `external_id`, `state`, `creator_id`, `template_id`
85
-
86
- ### `date_filter`
87
-
88
- Date range filter.
89
-
90
- ```json
91
- {
92
- "operation": "date_filter",
93
- "params": {
94
- "key": "updated_at",
95
- "gte": "2025-01-01T00:00:00Z",
96
- "lte": "2025-12-31T23:59:59Z",
97
- "time_zone": "UTC"
98
- }
99
- }
100
- ```
101
-
102
- `gte` = lower bound, `lte` = upper bound. Both optional.
103
-
104
- **v2 format note:** APIv2 used `lbound`/`ubound` instead of `gte`/`lte`. The
105
- eco-helpers compat layer translates these automatically.
106
-
107
- ### `register_filter`
108
-
109
- Scope org search to specific registers (org search only — not applicable to `previewPages`).
110
-
111
- ```json
112
- { "operation": "register_filter", "params": { "ids": ["REGISTER_ID"] } }
113
- ```
114
-
115
- ### `and_filter`
116
-
117
- Explicit AND group (top-level `filters` array is already implicit AND).
118
-
119
- ```json
120
- {
121
- "operation": "and_filter",
122
- "params": {
123
- "filters": [
124
- { "operation": "exact_filter", "params": { "key": "external_id", "value": "X" } },
125
- { "operation": "date_filter", "params": { "key": "updated_at", "gte": "..." } }
126
- ]
127
- }
128
- }
129
- ```
130
-
131
- ### `or_filter`
132
-
133
- OR group.
134
-
135
- ```json
136
- {
137
- "operation": "or_filter",
138
- "params": {
139
- "filters": [
140
- { "operation": "exact_filter", "params": { "key": "external_id", "value": "A" } },
141
- { "operation": "exact_filter", "params": { "key": "external_id", "value": "B" } }
142
- ]
143
- }
144
- }
145
- ```
146
-
147
- ### `tag_filter` (v2 pass-through)
148
-
149
- Filter by location tags. Used by APIv2 eco-helpers scripts. Passes through to the ES
150
- backend unchanged. Same ES backend as GraphQL register search.
151
-
152
- ```json
153
- {
154
- "type": "tag_filter",
155
- "tags": ["Auckland", "Wellington"],
156
- "mode": "any",
157
- "negate": false,
158
- "key": "tags"
159
- }
160
- ```
161
-
162
- ---
163
-
164
- ## SearchConf Builder (Gem)
165
-
166
- The gem provides `Input::SearchConf` for building filters programmatically:
167
-
168
- ```ruby
169
- # Find by externalId
170
- conf = SearchConf.new.filter(SearchConf::Exact.new(:external_id, 'EXT-001'))
171
-
172
- # Register-scoped date range
173
- conf = SearchConf.new
174
- .filter(SearchConf::Register.new('REG_ID'))
175
- .filter(SearchConf::DateRange.new(:updated_at, gte: '2025-01-01', time_zone: 'UTC'))
176
- .sort(:created_at, :asc)
177
-
178
- # OR filter (multiple externalIds)
179
- conf = SearchConf.new.filter(
180
- SearchConf::Or.new(
181
- SearchConf::Exact.new(:external_id, 'EXT-001'),
182
- SearchConf::Exact.new(:external_id, 'EXT-002')
183
- )
184
- )
185
-
186
- # Parametrize (same search, different value)
187
- base = SearchConf.new.filter(SearchConf::Exact.new(:external_id, 'X'))
188
- copy = base.with(external_id: 'Y') # original unchanged
189
-
190
- # Use with queries
191
- org.pages(searchConf: conf.to_h, first: 10)
192
- ```
193
-
194
- **Filter classes:** `SearchConf::Exact`, `SearchConf::DateRange`, `SearchConf::Register`,
195
- `SearchConf::And`, `SearchConf::Or`
196
-
197
- ---
198
-
199
- ## Known Filterable Field Keys (confirmed from production queries)
200
-
201
- | key | Type | Operations |
202
- |-----|------|------------|
203
- | `external_id` | string | `exact_filter` |
204
- | `updated_at` | datetime | `date_filter` |
205
- | `created_at` | datetime | `date_filter` (likely) |
206
- | `state` | enum | `exact_filter` (key: active/archived/draft) |
207
- | `location_ids` | ID array | likely `in_filter` or similar |
208
- | `creator_id` | ID | `exact_filter` |
209
- | `template_id` | ID | `exact_filter` |
210
-
211
- **Note:** The `fieldName` convention in some Confluence docs uses this same key concept.
212
- The keys are snake_case corresponding to indexed ES document fields — NOT necessarily
213
- the GraphQL field names.
214
-
215
- ---
216
-
217
- ## Future: Fluent DSL (Planned)
218
-
219
- A more ergonomic API using Ruby's `&` (AND) and `|` (OR) operators is planned:
220
-
221
- ```ruby
222
- Search[:external_id].eq('EXT-001') & Search.in_register('REG_ID')
223
- Search[:state].is(:active) | Search[:state].is(:draft)
224
- (Search[:state].is(:active) | Search[:state].is(:draft)) & Search.in_register('REG_ID')
225
- ```
226
-
227
- This is operator-based (familiar to Arel/Sequel users), avoids ambiguity in chaining,
228
- and will support AI-assisted filter generation. See `search-filter-builder/` project.