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,244 +0,0 @@
1
- # Project: eco-helpers GraphQL Compat Layer
2
-
3
- **Created:** 2026-06-07
4
- **Status:** Starting — URGENT (APIv2 retires in ~3 weeks)
5
-
6
- ---
7
-
8
- ## Goal
9
-
10
- Existing eco-helpers scripts work against `ecoportal-api-v2` (REST). They must continue
11
- to work against `ecoportal-api-graphql` (GraphQL) **without changing one line of script code**
12
- related to pages.
13
-
14
- The compat layer wraps `Ecoportal::API::GraphQL` and exposes the same interface that
15
- eco-helpers scripts currently call against `Ecoportal::API::V2`.
16
-
17
- ---
18
-
19
- ## eco-helpers API Surface (audited 2026-06-07)
20
-
21
- ### Pages
22
-
23
- | v2 call | Description | GraphQL equivalent |
24
- |---|---|---|
25
- | `apiv2.pages.get(id)` | Fetch page by ID | `Query::Page.new(client).query(id: id)` |
26
- | `apiv2.pages.get_new(template_id)` | Build draft from template | `Mutation::Page::BuildFromTemplate` (via `buildPageFromTemplate`) |
27
- | `apiv2.pages.create(page, from: template_id)` | Create from filled draft | `Mutation::Page::CreateFromTemplate` |
28
- | `apiv2.pages.update(page)` | Update changed page | `Mutation::Page::Update` with `Input::Page::Update.from_model` |
29
- | `apiv2.pages.get_body(page)` | Get patch doc of changes | `page.as_update` |
30
-
31
- ### Registers / Search
32
-
33
- | v2 call | Description | GraphQL equivalent |
34
- |---|---|---|
35
- | `apiv2.registers.search(register_id, opts) { \|r\| }` | Paginated search | `Query::RegisterPreviewPages` (fast/ES) for existence; org search for full data |
36
-
37
- Search options v2 → GraphQL mapping:
38
- - `sort: 'created_at'`, `dir: 'asc'` → `SearchConf.new.sort(:created_at, :asc)`
39
- - `query: text` → `SearchConf.new.query(text)`
40
- - `filters: [...]` → `SearchConf` with appropriate filter objects
41
-
42
- ### Page object API
43
-
44
- | v2 accessor | v2 type | GraphQL equivalent |
45
- |---|---|---|
46
- | `.id` | String | `Model::Page::Basic#id` |
47
- | `.state` | String | `#state` |
48
- | `.template_id` | String | `#sourceTemplateId` |
49
- | `.created_at` | String | `#createdAt` |
50
- | `.updated_at` | String | `#updatedAt` |
51
- | `.stages?` | Boolean | `#stages.any?` |
52
- | `.current_stage_id` | String | `Model::Page::Phased#currentStage.id` |
53
- | `.stages` | Collection | `#stages` |
54
- | `.stages.get_by_name(name)` | Stage | filtered stages |
55
- | `.sections` | Collection | stage `.sections` or BasicPage `.sections` |
56
- | `.components` | Collection | sections → dataFields (APIv2 name for fields) |
57
- | `.tags` | Array | `#otherTags` (or `#baseTags` for base tags) |
58
- | `.base_tags` | Array | `#baseTags` |
59
- | `.external_id` | String | `#externalId` |
60
- | `.validate` | String or nil | custom validation (not in GraphQL schema) |
61
- | `page.consolidate!` | void | `page.original_doc = page.doc.dup` (reset dirty tracking) |
62
-
63
- ### Dirty state tracking
64
-
65
- v2 uses `dirty?(page)` which checks if `patch_doc(page)['page']` is non-empty.
66
- GraphQL uses `page.as_update` — returns nil if no changes, hash if changed.
67
-
68
- Equivalence:
69
- ```ruby
70
- # v2
71
- dirty?(page)
72
- # GraphQL compat
73
- !page.as_update.nil?
74
- ```
75
-
76
- ### Response objects
77
-
78
- | v2 | GraphQL |
79
- |---|---|
80
- | `response.success?` | `payload.success?` |
81
- | `response.status` | `payload.error? ? :error : :ok` |
82
- | `response.body` | `payload.error_doc` |
83
-
84
- ---
85
-
86
- ## Design: CompatPages and CompatRegisters
87
-
88
- Two wrapper objects that expose the v2-compatible interface:
89
-
90
- ```ruby
91
- module Ecoportal::API::GraphQL::Compat
92
- class Pages
93
- def initialize(client); @client = client; end
94
- def get(id, **kargs) = ...
95
- def get_new(template_id) = ...
96
- def create(page, from:) = ...
97
- def update(page) = ...
98
- def get_body(page) = page.as_update
99
- end
100
-
101
- class Registers
102
- def initialize(client); @client = client; end
103
- def search(register_id, options = {}, &block) = ...
104
- end
105
- end
106
- ```
107
-
108
- And on the top-level `Ecoportal::API::GraphQL` class:
109
- ```ruby
110
- def pages = Compat::Pages.new(client)
111
- def registers = Compat::Registers.new(client)
112
- ```
113
-
114
- **Key constraints:**
115
- - `pages.get` must return an object that responds to all page accessors above
116
- - `pages.create` must return a reference object with `.page_id` and `.active_stage_id`
117
- - `page.consolidate!` must reset dirty state
118
- - `registers.search` must yield results in the same shape as v2 (block with `.id`)
119
-
120
- ---
121
-
122
- ## Architectural Differences: APIv2 vs GraphQL (critical for compat)
123
-
124
- ### Stage rendering and updating
125
-
126
- **APIv2:** Instance pages (pages with stages) are rendered ONE STAGE PER REQUEST.
127
- Every `pages.get(id, stage_id:)` call fetches a single stage view. Updating is also
128
- done per-stage. This is a known limitation.
129
-
130
- **GraphQL:** Fetches the full page in one request. Can update all stages at once via
131
- `updatePage`. Can also do stage-specific updates via `stageId` in `UpdatePageInput`.
132
-
133
- **Implication for compat:**
134
- - eco-helpers `RegisterUpdate` loops through stages by fetching each one. In GraphQL
135
- compat, we fetch the full page once and let the script work on a client-side stage view.
136
- - Stage-specific submit (e.g. close-out stage N to trigger fill-in task on stage N+1)
137
- STILL requires `stageId` in the GraphQL mutation. The submit trigger (creating/starting
138
- the next fill-in task) is server-side logic keyed on `stageId`.
139
- - `Compat::Pages#get(id, stage_id:)` currently ignores `stage_id` — this is a GAP.
140
- A future improvement: if `stage_id` is provided, filter the returned page to show only
141
- that stage's fields. But the mutation must still include `stageId`.
142
-
143
- ### Stage permissions
144
-
145
- **APIv2:** Per-stage permissions enforced at fetch time — requesting a forbidden stage
146
- returns no data or an error.
147
-
148
- **GraphQL:** Stage data is included or excluded silently based on permissions — no
149
- access errors, just reduced data. A stage the user can't access simply won't appear in
150
- `stages` or `stagesIndex`. (Needs double-check with Engineering.)
151
-
152
- **Implication:** Scripts that rely on access errors from APIv2 to detect permission
153
- boundaries may not get the same signal from GraphQL. Scripts should check whether the
154
- stage they expect is present in the response.
155
-
156
- ### `as_update` usage in eco-helpers
157
-
158
- `as_update` is called in eco-helpers for two purposes:
159
- 1. **Simulate/dry_run mode:** Print what would change without sending the request.
160
- 2. **Dirty detection:** `dirty?(page)` = `!page.as_update.nil?` (v2 pattern).
161
-
162
- Our compat `dirty?` already delegates to `as_update` via `PageCompat`. ✓
163
-
164
- For simulate mode: when `graphql.pages.update(page)` is called in dry_run mode,
165
- eco-helpers should call `pages.get_body(page)` (= `page.as_update`) and display the
166
- diff without making the HTTP request.
167
-
168
- ### `add_component` / `add_field` gap
169
-
170
- APIv2 allows adding NEW fields to a page via `components.add(...)`. In GraphQL,
171
- `updatePage` has `dataFields: { additions: [DataFieldInput], updates: [...], deletions: [ID] }`.
172
- We implemented `updates` only. `additions` is not yet supported in the compat layer.
173
-
174
- **Consequence:** Scripts that call `add_field` or `components.add` will not work yet.
175
- This is a known gap. Document it for the integration test phase.
176
-
177
- ### `apiv2.pages.create|update` redirect
178
-
179
- The eco-helpers `Eco::API::Session` manages an `apiv2` object. Redirecting to GraphQL
180
- requires swapping the `apiv2` session backend without changing scripts. Two approaches:
181
-
182
- 1. **Session-level swap (eco-helpers):** Override `apiv2.pages` and `apiv2.registers`
183
- in the eco-helpers session to return the GraphQL compat objects. Scripts are unchanged.
184
- This is the cleanest approach for the 3-week deadline.
185
-
186
- 2. **Proxy class:** Create `Ecoportal::API::GraphQL::V2Proxy` that mimics the full V2
187
- API surface. Assign this as the session backend when GraphQL mode is active.
188
-
189
- The GraphQL gem already provides `graphql.pages` and `graphql.registers`. The eco-helpers
190
- change is only in how the session wires the backend — NOT in the scripts themselves.
191
-
192
- ### Template updates
193
-
194
- Engineering does NOT want template updates via GraphQL currently — templates are still
195
- managed via the old AngularJS front-end. Do NOT expose template mutation endpoints from
196
- the compat layer. `Query::Templates` (read-only list) is fine. Mutations against templates
197
- should be blocked or at minimum undocumented.
198
-
199
- ### Forces (AngularJS front-end snippets)
200
-
201
- Forces are embedded front-end code snippets that attach to specific stages based on
202
- their bindings. They are being REPLACED by the Workflow Builder (see below). Low priority
203
- for scripting integration. Forces are not queryable/mutable via GraphQL in a meaningful way.
204
-
205
- ### Workflow Builder (in progress, high change velocity)
206
-
207
- The Workflow Builder (`pagesWorkflow`) allows configuring automated behaviours:
208
- - Deployed: notifications, auto page creation, task assignments
209
- - Not yet deployed: conditional field/section workflows (show/hide sections based on
210
- field values, computed risk ratings, gauge/select interdependencies)
211
- - Recently refactored (quick fixes via CI/CD for new customers)
212
-
213
- **Implication for scripting:** Workflow Builder operations are not part of the
214
- eco-helpers compat scope. However, scripts must be aware that workflow triggers
215
- (task creation, stage transitions) can fire as side effects of `updatePage`.
216
-
217
- ### DSL location decision (confirmed 2026-06-07)
218
-
219
- The compat DSL (page accessors, `components`, `stages[name]`, `consolidate!`, etc.)
220
- sits in `ecoportal-api-graphql` as concerns included on page models. This is correct
221
- because these are upstream-dependency concerns of this gem.
222
-
223
- The eco-helpers `OozeBase` / `RegisterUpdate` inheritance chain will transparently
224
- use the GraphQL-backed models once the session backend is swapped. No script code changes.
225
-
226
- However: some DSL methods (`add_field`, `add_component`, stage-specific submit) may
227
- need to live in eco-helpers itself if they cannot be cleanly expressed at the gem level.
228
-
229
- ---
230
-
231
- ## Open Questions (Resolved 2026-06-07)
232
-
233
- 1. ✓ Filter format: translated in `Compat::Registers` (date_filter lbound/ubound → gte/lte)
234
- 2. ✓ Components naming: `components` aliased to `field_collection` via `DataFieldAccess`
235
- 3. ✓ `get_new` shape: `buildFromTemplate` returns full page with field IDs via `CommonPageUnion`
236
- 4. ✓ Stage access: `StageCollection` wrapper provides `[]`, `get_by_name`, `ordered`
237
- 5. Script repo access: pending `.claude/settings.json` rules configuration
238
-
239
- ## Remaining Gaps
240
-
241
- - `stage_id` in `pages.get` currently ignored — stage-specific view not filtered client-side
242
- - `stageId` in `updatePage` not yet passed through compat — needed for stage submit/close-out
243
- - `add_field` / `add_component` (dataFields.additions) not implemented
244
- - Stage-specific permissions may behave differently (silent omission vs error)
@@ -1,266 +0,0 @@
1
- # eco-helpers → GraphQL Migration Guide
2
-
3
- *Migrating eco-helpers scripts from `ecoportal-api-v2` to `ecoportal-api-graphql`.*
4
-
5
- ---
6
-
7
- ## TL;DR
8
-
9
- For most page scripts: **change one line** (the API initialiser). Everything else stays the same.
10
-
11
- ```ruby
12
- # Before (APIv2)
13
- api = Ecoportal::API::V2.new(email: ENV['EMAIL'], pass: ENV['PASS'], org_id: ENV['ORG'])
14
-
15
- # After (GraphQL)
16
- api = Ecoportal::API::GraphQL.new(email: ENV['EMAIL'], pass: ENV['PASS'], org_id: ENV['ORG'])
17
- ```
18
-
19
- `api.pages` and `api.registers` expose the same interface — the same method names,
20
- the same argument shapes, the same block and return-value patterns.
21
-
22
- ---
23
-
24
- ## What Changes
25
-
26
- ### Initialiser
27
-
28
- | | APIv2 | GraphQL |
29
- |---|---|---|
30
- | Class | `Ecoportal::API::V2` | `Ecoportal::API::GraphQL` |
31
- | Gem | `ecoportal-api-v2` | `ecoportal-api-graphql` |
32
- | `email:` | ✓ | ✓ |
33
- | `pass:` | ✓ | ✓ |
34
- | `org_id:` | ✓ | ✓ |
35
- | `host:` | ✓ (default `live.ecoportal.com`) | ✓ (same default) |
36
-
37
- ### Gem dependency in your script's Gemfile
38
-
39
- ```ruby
40
- # Remove:
41
- gem 'ecoportal-api-v2'
42
-
43
- # Add:
44
- gem 'ecoportal-api-graphql'
45
- ```
46
-
47
- ---
48
-
49
- ## What Stays the Same
50
-
51
- ### `pages` interface
52
-
53
- | Method | APIv2 signature | GraphQL compat | Notes |
54
- |---|---|---|---|
55
- | `get(id)` | `pages.get(id)` | ✓ identical | |
56
- | `get(id, stage_id:)` | `pages.get(id, stage_id: sid)` | ✓ identical | Returns `StageView` |
57
- | `get_new(template_id)` | `pages.get_new(tid)` | ✓ identical | Returns built PageUnion |
58
- | `create(page, from:)` | `pages.create(page, from: tid)` | ✓ identical | Returns `PageReference` |
59
- | `update(page)` | `pages.update(page)` | ✓ identical | Returns `Response` |
60
- | `get_body(page)` | `pages.get_body(page)` | ✓ identical | Returns change hash |
61
-
62
- ### Page model accessors
63
-
64
- | Accessor | Works? | Notes |
65
- |---|---|---|
66
- | `page.id` | ✓ | |
67
- | `page.name` | ✓ | |
68
- | `page.external_id` | ✓ | maps to `externalId` |
69
- | `page.template_id` | ✓ | maps to `sourceTemplateId` |
70
- | `page.stages?` | ✓ | |
71
- | `page.stages['Stage Name']` | ✓ | via `Compat::StageCollection` |
72
- | `page.stages.get_by_name(n)` | ✓ | |
73
- | `page.stages.ordered.map(&:id)` | ✓ | |
74
- | `page.current_stage_id` | ✓ | on PhasedPage only |
75
- | `page.components` | ✓ | returns `DataField::Collection` |
76
- | `page.components.get_by_name(l)` | ✓ | case-insensitive |
77
- | `page.components.get_by_type(t)` | ✓ | symbol or string |
78
- | `page.dirty?` | ✓ | |
79
- | `page.consolidate!` | ✓ | resets change tracking |
80
- | `page.validate` | ✓ (always nil) | server validates on mutation |
81
- | `stage.components` | ✓ | |
82
- | `stage.sections` | ✓ | |
83
- | `stage.active?` / `stage.started?` | ✓ | |
84
-
85
- ### Page reference (after create)
86
-
87
- ```ruby
88
- ref = api.pages.create(draft, from: template_id)
89
- ref.page_id # ✓
90
- ref.active_stage_id # ✓
91
- ref.success? # ✓
92
- ```
93
-
94
- ### `registers` interface
95
-
96
- | Method | APIv2 | GraphQL compat | Notes |
97
- |---|---|---|---|
98
- | `search(id, opts) { \|r\| }` | ✓ | ✓ | Cursor-paginated |
99
- | `result.id` in block | ✓ | ✓ | |
100
- | No-block → `.total` | ✓ | ✓ | via `SearchResults` |
101
- | `.total_before_filtering` | ✓ | ✓ | |
102
-
103
- ---
104
-
105
- ## What Requires Review
106
-
107
- ### 1. Filter format in `registers.search`
108
-
109
- Scripts that pass v2-style filter hashes are **automatically translated** by
110
- `Compat::FilterTranslator`. You only need to review if your script passes filters
111
- in a non-standard or custom format.
112
-
113
- **Automatic translation (no action needed):**
114
-
115
- ```ruby
116
- # v2 date_filter with lbound/ubound — automatically converted to gte/lte
117
- { 'type' => 'date_filter', 'key' => 'updated_at', 'lbound' => '2025-01-01', 'ubound' => '2025-12-31' }
118
- # → { operation: 'date_filter', params: { key: 'updated_at', gte: '2025-01-01', lte: '2025-12-31' } }
119
-
120
- # tag_filter — passed through unchanged (same ES backend)
121
- { 'type' => 'tag_filter', 'tags' => [...] }
122
-
123
- # register_filter — passed through unchanged
124
- { 'type' => 'register_filter', 'params' => { 'ids' => [...] } }
125
- ```
126
-
127
- **Requires review:** if your script builds filters with keys like `extact_filter`
128
- (typo), non-standard operation names, or custom filter shapes not listed above —
129
- those will pass through unchanged and may return empty results silently.
130
- See `search_filters.md` for the full list of valid operation names (all snake_case).
131
-
132
- **Quick search to run on your script repo:**
133
-
134
- ```bash
135
- grep -rn "date_filter\|tag_filter\|filters.*type\|type.*filter" scripts/
136
- ```
137
-
138
- ### 2. `pages.update` — always fetch before update
139
-
140
- The GraphQL path requires `patchVer` for concurrency control.
141
- `Input::Page::Update.from_model` injects it automatically **if you fetched the page first**.
142
-
143
- ```ruby
144
- # Correct — always fetch first
145
- page = api.pages.get(page_id)
146
- page.name = 'New Name'
147
- api.pages.update(page)
148
-
149
- # Incorrect — updating a page you constructed manually (no patchVer)
150
- page = MyManualPageObject.new(...)
151
- api.pages.update(page) # ⚠ patchVer will be nil — server may reject
152
- ```
153
-
154
- If your script fetches pages via `get` before updating, no change needed.
155
- If your script constructs page objects manually without fetching — review those paths.
156
-
157
- ### 3. `get_new` — template ID must be the live template ID
158
-
159
- `pages.get_new(template_id)` calls `buildPageFromTemplate` on the GraphQL API.
160
- The `template_id` must be the live MongoDB ObjectId, same as in APIv2.
161
-
162
- The return value has the same structure as in APIv2 — `page.components`,
163
- `page.stages`, etc. Field IDs are server-assigned instance IDs, not template IDs.
164
- You must use these IDs when creating the page (the compat layer handles this
165
- automatically via `page.data_fields_updates`).
166
-
167
- ---
168
-
169
- ## Known Limitations
170
-
171
- ### 1. Field types — read access only for some types
172
-
173
- The following field types are **readable** but cannot be written via `as_input`:
174
-
175
- | Type | Status | Notes |
176
- |---|---|---|
177
- | `Signature` | Read only | Captured via UI |
178
- | `Mailbox` | Read only | System-managed |
179
- | `ActionsList` | Read only | Manage via `api.action` builder |
180
- | `Law` | Read only | Legal snippets, not editable via script |
181
- | `AiSummary` | Read only | AI-generated |
182
- | `Table` | Read only | Complex structure |
183
-
184
- The following types **are writable** but require a file upload step first:
185
-
186
- | Type | Status | Notes |
187
- |---|---|---|
188
- | `File` | Writable* | Requires REST upload → fileContainerIds |
189
- | `ImageGallery` | Writable* | Same as File |
190
-
191
- \* File upload support is on the roadmap. For now, `file_container_ids=` will work
192
- if you already have `fileContainerIds` from a prior upload.
193
-
194
- ### 2. `pages.search` — not implemented on `Compat::Pages`
195
-
196
- APIv2's `pages.search(...)` is not part of the compat layer.
197
- Use `api.registers.search(register_id, ...)` for register-scoped search,
198
- or use `Model::Organization.pages(searchConf: conf)` for org-wide search.
199
-
200
- ### 3. Performance — org search is ~3s/page
201
-
202
- The GraphQL `pages.get` (and `pages.get_new`) fetches full DB-rendered page data.
203
- This is ~3s per page for large pages, compared to ~200ms in APIv2 for the same data.
204
-
205
- Mitigation: batch fetches, or use `api.registers.search` (ES-backed, fast) to
206
- get page IDs first, then fetch only the pages you actually need to update.
207
-
208
- ### 4. `simulate?` / dry-run mode — no change
209
-
210
- The compat layer honours `get_body` / `dirty?` / `consolidate!` as before.
211
- Dry-run logic that checks `dirty?` and skips `update` if not dirty continues to work.
212
-
213
- ---
214
-
215
- ## Quick Verification Checklist
216
-
217
- Before switching a script to the GraphQL gem:
218
-
219
- - [ ] Grep for `API::V2.new` — replace with `API::GraphQL.new`
220
- - [ ] Grep for custom filter formats — verify they translate correctly
221
- - [ ] Grep for manual page construction (without `pages.get`) — check patchVer supply
222
- - [ ] Grep for `pages.search` — replace with `registers.search` or org search
223
- - [ ] Run the script in `simulate` mode against a staging org
224
-
225
- ---
226
-
227
- ## Example: minimal diff for a typical update script
228
-
229
- ```ruby
230
- # Before
231
- require 'ecoportal/api-v2'
232
- api = Ecoportal::API::V2.new(email: ENV['EMAIL'], pass: ENV['PASS'], org_id: ENV['ORG'])
233
-
234
- api.registers.search('REG_ID', filters: [date_range_filter('updated_at', from: '2025-01-01')]) do |result|
235
- page = api.pages.get(result.id)
236
- next unless page.dirty?
237
- page.components.get_by_name('Status')&.select_option('Active')
238
- api.pages.update(page)
239
- page.consolidate!
240
- end
241
-
242
- # After (change highlighted)
243
- require 'ecoportal/api-graphql' # ← changed gem
244
- api = Ecoportal::API::GraphQL.new(email: ENV['EMAIL'], pass: ENV['PASS'], org_id: ENV['ORG']) # ← changed class
245
-
246
- api.registers.search('REG_ID', filters: [date_range_filter('updated_at', from: '2025-01-01')]) do |result|
247
- page = api.pages.get(result.id)
248
- next unless page.dirty?
249
- page.components.get_by_name('Status')&.select_option('Active')
250
- api.pages.update(page)
251
- page.consolidate!
252
- end
253
- # Everything else is identical.
254
- ```
255
-
256
- ---
257
-
258
- ## Rollback
259
-
260
- If a script needs to revert to APIv2:
261
-
262
- 1. Change the require back to `ecoportal/api-v2`
263
- 2. Change the initialiser class back to `Ecoportal::API::V2`
264
- 3. No other code changes needed
265
-
266
- The compat layer was designed so the rollback is always a one-line change.
@@ -1,86 +0,0 @@
1
- # TODOs — eco-helpers GraphQL Compat Layer
2
-
3
- > URGENT — APIv2 retires in ~3 weeks.
4
- > Natural order: 1 → 2 → 3 → 4 → 5 (resolve open questions before implementing)
5
-
6
- ---
7
-
8
- ## Step 1 — Resolve open questions (Done 2026-06-07)
9
-
10
- - [x] **1.1** Filter format confirmed: scripts use `tags_filter` and `date_range_filter`
11
- helpers producing `type: "date_filter"` with `lbound`/`ubound` keys.
12
- Translation: date_filter lbound→gte, ubound→lte. Other types pass through.
13
-
14
- - [x] **1.2** `registers.search` block result: yields individual PreviewPage objects (.id etc).
15
- No-block form returns an object with `.total` and `.total_before_filtering`.
16
- Implemented: `Compat::SearchResults` wraps connection with both properties.
17
-
18
- - [ ] **1.3** `pages.get_new` draft shape — still waiting on end-script repo access.
19
- For now: `buildPageFromTemplate` returns PageUnion with field IDs and stagesIndex.
20
-
21
- - [x] **1.4** `pages.create` reference: only `.page_id` and `.active_stage_id` used.
22
- `Compat::PageReference` implements both correctly.
23
-
24
- - [x] **1.5** Stage access: `stages[id_name]` AND `stages.get_by_name(id_name)` AND
25
- `stages.ordered.map(&:id)`. Implemented via `Compat::StageCollection` wrapper
26
- included via `Model::Page::Phased#stages` override.
27
-
28
- ---
29
-
30
- ## Step 2 — Scaffold `Compat::Pages` and `Compat::Registers` (Done 2026-06-07)
31
-
32
- - [x] **2.1** `lib/ecoportal/api/graphql/compat/pages.rb`: `get`, `get_new`, `create`, `update`, `get_body`.
33
- *Done 2026-06-07*
34
-
35
- - [x] **2.2** `lib/ecoportal/api/graphql/compat/registers.rb`: `search` with cursor pagination
36
- and v2 filter translation (date_filter lbound/ubound → gte/lte).
37
- *Done 2026-06-07*
38
-
39
- - [x] **2.3** Wired from top-level `Ecoportal::API::GraphQL`: `pages` → `Compat::Pages`, `registers` → `Compat::Registers`.
40
- *Done 2026-06-07*
41
-
42
- ---
43
-
44
- ## Step 3 — Page model compatibility wrapper (Done 2026-06-07)
45
-
46
- - [x] **3.1** `Concerns::PageCompat` included in `Interface::BasePage`:
47
- `external_id`, `template_id`, `components`, `stages?`, `consolidate!`, `validate`, `dirty?`.
48
- `current_stage_id` defined on `Model::Page::Phased` (reads in-progress stage from doc).
49
- 19 specs: `spec/ecoportal/api/graphql/concerns/page_compat_spec.rb`.
50
- *Done 2026-06-07*
51
-
52
- - [x] **3.2** `Compat::StageCollection`: `stages[id_or_name]`, `get_by_name`, `ordered`.
53
- Wrapped via `Model::Page::Phased#stages` override.
54
- Specs: `spec/ecoportal/api/graphql/compat/stage_collection_spec.rb`.
55
- *Done 2026-06-07*
56
-
57
- - [x] **3.3** `Compat::PageReference`: `.page_id` and `.active_stage_id` (via `current_stage_id`).
58
- Fixed: was using `currentStage` (undefined accessor); now uses `current_stage_id`.
59
- Specs: `spec/ecoportal/api/graphql/compat/page_reference_spec.rb`.
60
- *Done 2026-06-07*
61
-
62
- ---
63
-
64
- ## Step 4 — Search/filter translation
65
-
66
- - [x] **4.1** `Compat::FilterTranslator.to_graphql(v2_filter)` — translates v2 filter hashes
67
- to `SearchConf` objects. Handles `date_filter` (lbound→gte, ubound→lte), `tag_filter`,
68
- `exact_filter`, `register_filter`; extracted from `Compat::Registers`.
69
- *Done 2026-06-07 — 12 specs in `spec/ecoportal/api/graphql/compat/filter_translator_spec.rb`*
70
-
71
- - [x] **4.2** `Logic::BaseQuery#normalize_variables` auto-calls `.to_h` on value objects
72
- (e.g. `SearchConf`) before passing as GraphQL variables — callers no longer need explicit `.to_h`.
73
- *Done 2026-06-07 (commit cc72902)*
74
-
75
- ---
76
-
77
- ## Step 5 — Integration test + migration guide
78
-
79
- - [ ] **5.1** Run a real end-script against the compat layer (requires end-script repo access).
80
- User will provide once `.claude/settings.json` rules are set.
81
-
82
- - [x] **5.2** `.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md` written.
83
- Covers: one-line initialiser change, API compatibility table, filter review checklist,
84
- patchVer requirement, known limitations (read-only field types, no pages.search,
85
- ~3s/page performance), example diff, rollback instructions.
86
- *Done 2026-06-08*
@@ -1,101 +0,0 @@
1
- # Project: DoubleModel Review & Stabilisation (`ecoportal-api-v2`)
2
-
3
- **Created:** 2026-06-05
4
- **Status:** idea — not started
5
- **Primary repo:** `ecoportal-api-v2`
6
- **Affects:** `ecoportal-api-graphql` (consumers of `as_update` / `as_input` cascade)
7
- **Blocks:** `dynamic-model-generation` project
8
-
9
- ---
10
-
11
- ## Goal
12
-
13
- Review and stabilise the `DoubleModel` implementation in `ecoportal-api-v2`, with particular
14
- focus on:
15
-
16
- 1. The `doc` / `original_doc` lookup chain through nested and Array-indexed models.
17
- 2. The `root!` / `read_only!` memoisation flags and their interaction with the cascaded diff.
18
- 3. Ensuring that memoised `@doc` cannot silently go out of sync with a parent's diff.
19
-
20
- The outcome must be a `DoubleModel` contract that is reliable enough to underpin
21
- `ecoportal-api-graphql`'s `as_update` / `as_input` pipeline and, eventually, auto-generated
22
- model classes.
23
-
24
- ---
25
-
26
- ## Background
27
-
28
- The `DoubleModel` stores the canonical document (`@doc`) only at the root object. All
29
- nested descendant objects resolve their own `doc` by walking up the ownership chain and
30
- extracting their slice of the root `@doc` by key path.
31
-
32
- This design is correct for Hash-keyed paths (keys are deterministic). It breaks down for
33
- Array-indexed paths (`CollectionModel` and similar), because Array indexes are positional —
34
- if order changes, a nested object's key path no longer resolves to the right element.
35
-
36
- The current workaround is to look up the element by its own key from the parent at access
37
- time, rather than by stored index. This lookup is performed every time `doc` or
38
- `original_doc` is called on a nested object inside an Array, making it operationally costly:
39
-
40
- - The lookup re-scans the parent array on each call.
41
- - Objects under a `CollectionModel` may be instantiated multiple times during a single render
42
- as a side effect of these repeated resolutions.
43
-
44
- To mitigate this, `root!` and `read_only!` class-level flags were introduced. They allow
45
- a model class to memoise `@doc` locally (breaking the chain) when it is known to be stable.
46
- These flags are used extensively in `ecoportal-api-graphql` model and payload classes.
47
-
48
- ### Known risk
49
-
50
- `root!` also signals to the cascaded diff (`DiffService`) that the object should be
51
- excluded from the parent's diff. This is correct for read-only lookup objects (e.g. a
52
- referenced Location node). But if `root!` is applied at a level where the object *is*
53
- expected to contribute changes to the parent diff, the memoised `@doc` will silently
54
- go out of sync — changes to that sub-model will not surface in `as_update`. This is
55
- a subtle correctness risk that needs to be audited across all uses of `root!` and
56
- `read_only!` in `ecoportal-api-graphql`.
57
-
58
- ---
59
-
60
- ## Scope
61
-
62
- - Audit all uses of `root!` and `read_only!` in `ecoportal-api-graphql` model and payload
63
- classes: confirm each is correctly applied (genuinely read-only / excluded from diff).
64
- - Review the Array-indexed lookup in `CollectionModel` (and related): document the current
65
- behaviour, identify cases where it causes multiple object instantiation.
66
- - Propose and implement a more efficient resolution strategy for Array-indexed nested models
67
- that does not require rescanning on every `doc` / `original_doc` call.
68
- - Ensure `patch_ver` propagation through the chain is correct after any changes.
69
- - Confirm that the `initial_doc` / `original_doc` / `doc` three-state contract is
70
- preserved correctly after any refactor.
71
-
72
- ---
73
-
74
- ## Spec-first requirement
75
-
76
- Specs related to any modification **must be written before the change is applied** to
77
- `ecoportal-api-v2`, to lock in current behaviour and guard backwards compatibility.
78
- Specs must also be maintained and extended after the change to prevent future regressions.
79
-
80
- This applies both to the `ecoportal-api-v2` spec suite and to any `ecoportal-api-graphql`
81
- specs that exercise the cascaded diff path.
82
-
83
- ---
84
-
85
- ## Open Questions
86
-
87
- - Is the Array-indexed lookup fixable with a simple cache keyed by object identity
88
- (`object_id`) that is invalidated on `doc=` / `reset!` calls?
89
- - Are there cases in `ecoportal-api-graphql` where `root!` is applied incorrectly
90
- (object should contribute to diff but doesn't)?
91
- - What is the right `read_only!` vs `root!` semantic split? (`read_only!` = never
92
- accept changes; `root!` = store doc locally + exclude from parent diff — are these
93
- always the same thing, or should they be separable?)
94
-
95
- ---
96
-
97
- ## Notes
98
-
99
- This project is primarily work in `ecoportal-api-v2`, but the review must happen with
100
- `ecoportal-api-graphql` open alongside it, since the usage patterns there drive what
101
- needs to be fixed.